并发数据覆盖问题,断点在你这个例子里面是调试不出来的。
i+=1;在断点模式下。它是一次操作。
你要明白多线程i+=1;为什么会出错。
线程1:读取i=0;
线程2:读取i=0;
线程1:计算i+1;
线程2:计算i+1;
线程1:计算结果回写i变量;i=1;
线程2:计算结果回写i变量;i=1;
不是说,每个线程都有自己空间,把变量都弄到自己内存计算后放回共享空间吗。那么println(i)里面的i也该出现问题才是呀
还是说,每个线程是否从共享空间存取数据都是不确定的。
@幻空城: 你的断点在i+=1之后,这个时候线程已经把数据计算完毕并回写了。
这样:
var i=0;
var tmp=i;
var xx=tmp;//断点在这里,线程1,2.都到这里之后,冻结线程1.
tmp+=1;
i=tmp;
print(i)//断点。线程到这里,激活线程1。也执行到这里。这个时候控制台输入2个1.
@calvinK: i 是共享数据,temp是局部变量吧,不然断点也不是 两个1.
不是说,每个线程都有自己空间,把变量都弄到自己内存计算后放回共享空间
这句话该怎么理解呢
@幻空城: i 共享变量,
tmp=i;
tmp复制i的值到当前线程空间。//把变量都弄到自己内存
i=tmp;
计算后放回共享空间
@calvinK: i不是也该被拷贝到当前线程空间的吗
@幻空城:
i+=1;
i的值copy到当前线程的计算栈上,
计算栈上的值+1.当前计算栈上的值回写到i的内存地址。
这样说可否理解。
@calvinK: 这个明白,只是不明白,什么时候从共享中获取?(是每次使用获取?还是获取一次然后一直操作缓存直到线程结束;什么时候写回去?是不是要等整个线程执行完后再写回去。
@幻空城: 值类型:get就是将值copy推送到计算栈上,set 就是将计算栈上的值回写到内存地址。
@calvinK: 汗,这个回答的。。。比如说:共享数据 i: //这里i的值是不是从共享内存中获取到i到线程内存中 int temp=i; //这里的i值是不是从共享中取,还是线程中上面已经缓存的i值,计算完是不是写回共享内存。 i+=1; //这里的i值是不是从共享中取,还是从线程中上面已经缓存的i值,计算完是不是写回共享内存。 i+=2;
@幻空城:
我们就说i+=1;首先这个操作在cpu指令上分3步. get i; i+=1; set i;这也是为什么这个操作不是原子性操作,为什么有并发问题的根本性原因。
//这里i的值是不是从共享内存中获取到i到线程内存中
////你概念错误.没有所谓线程缓存这个说法.
下面的图说明了一切。
每一个i+=1;都要完整的执行上面截图中的3步.能明白了不
@calvinK: 我想我明白了,我原来以为,每个线程自己分配有个内存,进行数据操作都是拷贝数据处理。把cup的寄存器当成线程的内存了。实际情况是不是这样:线程得到cpu的资源,然后把要处理的数据拷贝到寄存器,然后把处理后的数据写回到共享区中,释放掉寄存器的数据。线程释放cpu资源,然后再得到cpu资源。一条线程命令执行完(run()方法代码执行完),就是一值重复这样的操作。
非常感激你的详细解答。哈哈,等再疑问了再会。
哇,打断点的方式去模拟线程并发,还可以这么玩的啊???
嗯,以前没细玩,一玩发现就发现有些不明白了
@幻空城: 给你推荐园里的一个博客 http://www.cnblogs.com/xrq730/category/733883.html
@让我发会呆: 嗯 谢谢,我会好好看一下的
加法不是原子操作,不能够这样加
你在debug模式下会中断线程的运行,导致数据会被强制同步一次,因此你是无法察觉这种错误的。
还有这种事情呀,按你这种说法,那么debug岂不是不能调试多线程了
@幻空城: 所以说多线程麻烦咯,不是麻烦在写法上,而是麻烦在这些东西上,比如线程安全性,线程任务如何分配(不能一个线程累死,其他的饿死)。所以很多时候会使用其他方式进行规避,比如actor模式。
@Daniel Cai:
每个线程都有自己空间,把变量都弄到自己内存计算后放回共享空间
这句话该怎么理解呢
@幻空城: 不太明白你指的什么。只能泛泛说下,抛开线程,代码在执行的时候就需要栈,这样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),如果发现++操作后原值不对,那么就不停的再来,只到回写前内存中的值和之前取出的值一样,然后才完成回写。
@Daniel Cai: 比如说:共享数据 i:
//这里i的值是不是从共享内存中获取到i到线程内存中
int temp=i;
//这里的i值是不是从共享中取,还是线程中上面已经缓存的i值,计算完是不是写回共享内存。
i+=1;
//这里的i值是不是从共享中取,还是从线程中上面已经缓存的i值,计算完是不是写回共享内存。
i+=2;
@Daniel Cai: 非常感谢你说了这么多,虽然你只是说泛泛一下,觉得自己不懂的还很多。进一步了解后再来细问。谢了