首页 新闻 会员 周边

为什么通过断点方式模拟不出,多线程共享数据出错的情况。

0
[已解决问题] 解决于 2016-12-28 14:08
public class A {
int i;
void inrease() {
System.out.println(i); //断点:部分线程停留在此 i
+=1;
//断点:线程运行到此,为什么会及时更新了别的线程i的值?????? System.
out.println(i); } //线程run方法,a是共享对象A run(){ for (int i = 0; i < 10; i++) { a.inrease(); }

得到断点得到数据为什么会是正常的,多线程的情况下。

假程序猿的主页 假程序猿 | 菜鸟二级 | 园豆:226
提问于:2016-12-27 17:05
< >
分享
最佳答案
0

并发数据覆盖问题,断点在你这个例子里面是调试不出来的。

i+=1;在断点模式下。它是一次操作。

你要明白多线程i+=1;为什么会出错。

 

线程1:读取i=0;

线程2:读取i=0;

 

线程1:计算i+1;

线程2:计算i+1;

 

线程1:计算结果回写i变量;i=1;

线程2:计算结果回写i变量;i=1;

奖励园豆:5
czd890 | 专家六级 |园豆:14412 | 2016-12-27 17:14

不是说,每个线程都有自己空间,把变量都弄到自己内存计算后放回共享空间吗。那么println(i)里面的i也该出现问题才是呀

假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 17:28

还是说,每个线程是否从共享空间存取数据都是不确定的。

假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 17:31

@幻空城: 你的断点在i+=1之后,这个时候线程已经把数据计算完毕并回写了。

czd890 | 园豆:14412 (专家六级) | 2016-12-27 17:39

这样:

var i=0;

var tmp=i;

var xx=tmp;//断点在这里,线程1,2.都到这里之后,冻结线程1.

tmp+=1;

i=tmp;

print(i)//断点。线程到这里,激活线程1。也执行到这里。这个时候控制台输入2个1.

czd890 | 园豆:14412 (专家六级) | 2016-12-27 17:42

@calvinK: i 是共享数据,temp是局部变量吧,不然断点也不是 两个1.

不是说,每个线程都有自己空间,把变量都弄到自己内存计算后放回共享空间

这句话该怎么理解呢

假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 18:00

@幻空城: i 共享变量,

tmp=i;

tmp复制i的值到当前线程空间。//把变量都弄到自己内存

i=tmp;

计算后放回共享空间

czd890 | 园豆:14412 (专家六级) | 2016-12-27 18:05

@calvinK: i不是也该被拷贝到当前线程空间的吗

假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 18:12

@幻空城: 

i+=1;

i的值copy到当前线程的计算栈上,

计算栈上的值+1.当前计算栈上的值回写到i的内存地址。

这样说可否理解。

czd890 | 园豆:14412 (专家六级) | 2016-12-27 18:38

@calvinK: 这个明白,只是不明白,什么时候从共享中获取?(是每次使用获取?还是获取一次然后一直操作缓存直到线程结束;什么时候写回去?是不是要等整个线程执行完后再写回去。

假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 18:58

@幻空城: 值类型:get就是将值copy推送到计算栈上,set 就是将计算栈上的值回写到内存地址。

czd890 | 园豆:14412 (专家六级) | 2016-12-27 19:06
@calvinK: 汗,这个回答的。。。比如说:共享数据 i: 

//这里i的值是不是从共享内存中获取到i到线程内存中
int temp=i;

//这里的i值是不是从共享中取,还是线程中上面已经缓存的i值,计算完是不是写回共享内存。
i+=1;

//这里的i值是不是从共享中取,还是从线程中上面已经缓存的i值,计算完是不是写回共享内存。
i+=2;
假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 19:47

@幻空城: 

我们就说i+=1;首先这个操作在cpu指令上分3步. get i; i+=1; set i;这也是为什么这个操作不是原子性操作,为什么有并发问题的根本性原因。

//这里i的值是不是从共享内存中获取到i到线程内存中

////你概念错误.没有所谓线程缓存这个说法.

下面的图说明了一切。

 

czd890 | 园豆:14412 (专家六级) | 2016-12-27 21:17

每一个i+=1;都要完整的执行上面截图中的3步.能明白了不

czd890 | 园豆:14412 (专家六级) | 2016-12-27 21:18

@calvinK: 我想我明白了,我原来以为,每个线程自己分配有个内存,进行数据操作都是拷贝数据处理。把cup的寄存器当成线程的内存了。实际情况是不是这样:线程得到cpu的资源,然后把要处理的数据拷贝到寄存器,然后把处理后的数据写回到共享区中,释放掉寄存器的数据。线程释放cpu资源,然后再得到cpu资源。一条线程命令执行完(run()方法代码执行完),就是一值重复这样的操作。

假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 22:24

非常感激你的详细解答。哈哈,等再疑问了再会。

假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-28 14:02
其他回答(2)
0

哇,打断点的方式去模拟线程并发,还可以这么玩的啊???

让我发会呆 | 园豆:2929 (老鸟四级) | 2016-12-27 17:18

嗯,以前没细玩,一玩发现就发现有些不明白了

支持(0) 反对(0) 假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 17:29

@幻空城: 给你推荐园里的一个博客   http://www.cnblogs.com/xrq730/category/733883.html

支持(0) 反对(0) 让我发会呆 | 园豆:2929 (老鸟四级) | 2016-12-27 18:03

@让我发会呆: 嗯 谢谢,我会好好看一下的

支持(0) 反对(0) 假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 19:50
0

加法不是原子操作,不能够这样加

你在debug模式下会中断线程的运行,导致数据会被强制同步一次,因此你是无法察觉这种错误的。

Daniel Cai | 园豆:10424 (专家六级) | 2016-12-27 17:23

还有这种事情呀,按你这种说法,那么debug岂不是不能调试多线程了

支持(0) 反对(0) 假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 17:36

@幻空城: 所以说多线程麻烦咯,不是麻烦在写法上,而是麻烦在这些东西上,比如线程安全性,线程任务如何分配(不能一个线程累死,其他的饿死)。所以很多时候会使用其他方式进行规避,比如actor模式。

支持(0) 反对(0) Daniel Cai | 园豆:10424 (专家六级) | 2016-12-27 17:49

@Daniel Cai: 

每个线程都有自己空间,把变量都弄到自己内存计算后放回共享空间

这句话该怎么理解呢

支持(0) 反对(0) 假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 18:02

@幻空城: 不太明白你指的什么。只能泛泛说下,抛开线程,代码在执行的时候就需要栈,这样cpu才能按部就班的执行(filo),针对你的代码而言,一个简单的i=i+1 (i+=1),在栈上就和1楼说的类似。但这里有个地方需要注意,如果多个线程同时在做这个操作,那么很可能读到的数据并不是最新的,引发的原因可能为不同线程对应的core缓存了不同的数据(L1上),而你又没有给出任何hint告诉cpu这里应该如何处理,所以大家就一起玩起了和稀泥的游戏,最后导致结果不准确。或者回写的时候覆盖了才写入的新值。

针对单独的读取很简单,你可以给个hint:volatile,告诉cpu这个值你别cache了,直接从内存中捞,那么就能够避免大家读到不同的数据。

针对有读写的,简单点就一个大大的lock(synchronized)让cpu在处理这里时一次只处理一个,这样安全性就得到了保证,但性能会受到巨大的损失。因此可以换用CAS来实现(比如AtomicInteger),其内部是通过cpu本身提供的原子指令(compare and swap)对数据做++操作(直接对内存,而不是对cache),如果发现++操作后原值不对,那么就不停的再来,只到回写前内存中的值和之前取出的值一样,然后才完成回写。

支持(0) 反对(0) Daniel Cai | 园豆:10424 (专家六级) | 2016-12-27 18:31

@Daniel Cai: 比如说:共享数据 i:

//这里i的值是不是从共享内存中获取到i到线程内存中

int temp=i;

//这里的i值是不是从共享中取,还是线程中上面已经缓存的i值,计算完是不是写回共享内存。

i+=1;

//这里的i值是不是从共享中取,还是从线程中上面已经缓存的i值,计算完是不是写回共享内存。

i+=2;

支持(0) 反对(0) 假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 19:53

@Daniel Cai: 非常感谢你说了这么多,虽然你只是说泛泛一下,觉得自己不懂的还很多。进一步了解后再来细问。谢了

支持(0) 反对(0) 假程序猿 | 园豆:226 (菜鸟二级) | 2016-12-27 22:48
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册