小白教程

 找回密码
 立即注册
查看: 7656|回复: 5

[已解决]java成员变量线程安全问题的疑问

[复制链接]

1

主题

1

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2021-4-7 17:41:23 | 显示全部楼层 |阅读模式
为什么这段代码会有线程安全问题呢
这里为什么会有线程安全问题啊,按着这样的调用,无论那个线程remove()的时候,集合里面都会有一个元素,因为是先加后remove 怎么可能发生执行到remove的时候,集合没元素,就算一个线程没加上,另外一个线程也加上了啊
最佳答案
2021-4-15 14:45:03
ArrayList不是线程安全的链表。

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

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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0

主题

1

帖子

2

积分

新手上路

Rank: 1

积分
2
发表于 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。
线程从主存同步数据和修改后将数据同步回去的时机是随机的
回复

使用道具 举报

0

主题

1

帖子

2

积分

新手上路

Rank: 1

积分
2
发表于 2021-4-15 14:45:03 | 显示全部楼层 &
ArrayList不是线程安全的链表。

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

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

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

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

使用道具 举报

0

主题

2

帖子

3

积分

新手上路

Rank: 1

积分
3
发表于 2021-4-19 12:22:48 | 显示全部楼层
我是这样理解的,你可以看下线程可见性的文章,大意就是对于共享变量,线程执行时会从主存拷贝一个副本,操作完成后会有个更新主存的过程,但是这个更新不是立即更新的,很容易就出现覆盖的情况
回复

使用道具 举报

0

主题

1

帖子

2

积分

新手上路

Rank: 1

积分
2
发表于 2021-4-23 23:13:59 | 显示全部楼层
如果你想每个线程的这个list不相互影响,你就使用threadlocal去定义
回复

使用道具 举报

0

主题

1

帖子

2

积分

新手上路

Rank: 1

积分
2
发表于 2021-4-27 11:54:06 | 显示全部楼层
多线程么,又不安顺序执行的。很有可能两个Add完了,size还是1,remove掉一个,再remove就报错了。

从源代码可以看出,Add的时候,并没有立即更新size,如果两个线程同时进入add,那么size的值是一样的,那么其中一个结算结果就会被另一个覆盖了,而不是在其基础上进行叠加
  1. public boolean add(E e) {
  2.         modCount++;
  3.         add(e, elementData, size);
  4.         return true;
  5.     }
复制代码
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|小白教程 ( 粤ICP备20019910号 )

GMT+8, 2024-9-20 14:52 , Processed in 0.036060 second(s), 29 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc. Template By 【未来科技】【 www.wekei.cn 】

快速回复 返回顶部 返回列表