万敏 发表于 2021-4-7 17:41:23

java成员变量线程安全问题的疑问

为什么这段代码会有线程安全问题呢
这里为什么会有线程安全问题啊,按着这样的调用,无论那个线程remove()的时候,集合里面都会有一个元素,因为是先加后remove 怎么可能发生执行到remove的时候,集合没元素,就算一个线程没加上,另外一个线程也加上了啊

石说心语 发表于 2021-4-9 20:44:26

主要问题是在于list不是线程安全的,可以这样理解:线程A主存读取当前size 0,add 1 remove 0,但是并不刷新主存,线程B主存读取size 0 add 1 刷新主存,此时线程A也刷新主存,导致最终的size是0 线程B再执行remove操作的时候,有可能先从主存同步数据,拿到size 0。
线程从主存同步数据和修改后将数据同步回去的时机是随机的

小野李亮 发表于 2021-4-15 14:45:03

ArrayList不是线程安全的链表。

线程安全的意思就是多个线程访问相同数据时,无论怎么访问,数据的状态都要保持一致(和一个线程单独访问的结果是一样的)。
比如说,有两个线程,一个线程对同一个整数变量自增运算100次,另一个线程对同一个整数变量自减50次,如果算法是线程安全的,那么,两个线程同时运行之后,结果和单独一个线程先自增100次再自减50次是一样的。这就要求,每一次的自增(自减)操作,都是线程安全的才可以。

楼主可能不太理解,为啥两个线程同时访问同一个内存变量会出现差错。这是由于线程切换和多CPU的缓存造成的。一般都解释为线程切换造成的。
这是一个比较讲究细节的过程,一个线程运行的时候,同一时间另一个线程可能没有处于运行状态。操作系统中有两三千个线程需要运行,CPU核心数也就那么几个,所以要轮着来使用。线程切换的时候,需要先将要换出去的线程进行现场保护,将当时的线程状态保存起来,包括CPU寄存器中的数据,会被压入线程栈中,之后再将切换进来的线程进行现场还原,从线程栈中把CPU寄存器的数据还原回去。我们知道,CPU做的算术运算和逻辑运算都是在寄存器之间做的,做完之后再回写到内存中。这样的话,寄存器的数据相当于内存数据的一个镜像。多个线程访问同一个内存数据,就会有多个镜像,那么,在线程切换的时候,镜像没来得及回写到内存就被压栈了,其他线程更改镜像有的就可以及时回写,等刚才那个没来得及回写的线程被恢复运行时,第一件事就是回写他上一次执行的镜像,那么,其他线程的镜像被覆盖掉了,最终造成了内存数据的不一致。

多CPU时,由于每块CPU都自带缓存cache,也会造成这种现象。

我之所以写这些,就是因为,楼主在表述问题时,把程序正在操纵的数据与链表混为一谈,细节上,两者是截然不同的东西,虽然,你的代码操纵的就是链表,但是,实际上,操纵的是链表里面的东西(内容结构)。链表被覆盖指的是链表的引用被其他东西覆盖了,和链表内部,指向链表元素的引用被其他东西所覆盖,是两个概念。而且,不单单是引用,链表的大小也会产生线程安全的问题,所以,整个链表就不是线程安全的

飞米 发表于 2021-4-19 12:22:48

我是这样理解的,你可以看下线程可见性的文章,大意就是对于共享变量,线程执行时会从主存拷贝一个副本,操作完成后会有个更新主存的过程,但是这个更新不是立即更新的,很容易就出现覆盖的情况

杨天杰 发表于 2021-4-23 23:13:59

如果你想每个线程的这个list不相互影响,你就使用threadlocal去定义

风雨不动 发表于 2021-4-27 11:54:06

多线程么,又不安顺序执行的。很有可能两个Add完了,size还是1,remove掉一个,再remove就报错了。

从源代码可以看出,Add的时候,并没有立即更新size,如果两个线程同时进入add,那么size的值是一样的,那么其中一个结算结果就会被另一个覆盖了,而不是在其基础上进行叠加
public boolean add(E e) {
      modCount++;
      add(e, elementData, size);
      return true;
    }
页: [1]
查看完整版本: java成员变量线程安全问题的疑问