java多线程:volatile变量、happens-before关系及内存一致性

我们都知道计算机是由内存单元和 CPU (还有许多其他部分)组成。主内存就是程序指令、变量、数据存储的地方。程序执行期间,为了获得更好的性能,CPU 可能会将变量拷贝到自己的内存中(即所谓的 CPU 缓存)。由于现代计算机有多个 CPU,同样也存在多个 CPU 缓存。

在多线程环境下,有可能多个线程同时执行,每个线程使用不同的 CPU(虽然这完全依赖于底层的操作系统),每个 CPU 都从主内存中拷贝变量到它自己的缓存中。当一个线程访问这些变量时,是直接访问缓存中的副本,而不是真正访问主内存中的变量。

 

现在,假设在我们的测试中有两个线程运行在不同的 CPU 上,并且其中的有一个缓存了标识变量(或者两个都缓存了)。现在考虑如下的执行顺序

1、写线程生成一个值,并将 hasValue 设置为 true。但是只更新缓存中的值,而不是主内存。

2、读线程尝试消费一个值,但是它的缓存副本中 hasValue 被设置为 false,所以即使写线程生产了一个新的值,也不能被消费,因为读线程无法跳出睡眠循环(hasValue 的值为 false)。

3、因为读线程不能消费新生成的值,所以写线程也不能继续,因为标识变量没有设置回 false,因此写线程阻塞在睡眠循环中。

4、这样,就产生了死锁!

这种情况只有在 hasValue 同步到所有缓存才能改变,这完全依赖于底层的操作系统。

private volatile boolean hasValue = false;

那怎么解决这个问题? volatile 怎么会适合这个例子?
如果我们将 hasValue 标示为 volatile,我就能确定这种死锁就不会再发生。

1、写线程生成一个值,并将 hasValue 设置为 true,这次直接更新主内存中的值(即使这个变量被缓存了)。

2、读线程尝试消费一个值,先检查 hasValue 的值,每次读取都强制直接从主内存中获取值,所以能获取到写线程改变后的值。

3、读线程消费完生成的值后,重新设置标识变量的值,这个新的值也会同步到主内存(如果这个值被缓存了,缓存的副本也会更新)。

4、写线程获每次都是从主内存中取这个改变了的值,这样就能继续生成新的值

 

实际上,它还有更多的功能。访问一个 volatile 变量会在语句间建立 happens-before 关系。

 什么是 happens-before 关系?
happens-before 关系是程序语句之间的排序保证,这能确保任何内存的写,对其他语句都是可见的。

对一个volatile的写happens-before 任意对这个变量的读

这与 Volatile 是怎么关联的?
当写一个 volatile 变量时,随后对该变量读时会创建一个 happens-before 关系。所以,所有在 volatile 变量写操作之前完成的写操作,将会对随后该 volatile 变量读操作之后的所有语句可见。

posted @ 2016-08-10 17:06  程序猿进化之路  阅读(244)  评论(0)    收藏  举报