对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
然后又举了这么一个例子:
假如某个时刻变量inc的值为10,
线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;
然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。
然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
那么两个线程分别进行了一次自增操作后,inc只增加了1。
解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。
根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。
简化为三步:
第一步,线程1读取inc的值,然后被阻塞;
第二步,线程2读取Inc的值,为10,加1,写入主存;
第三步,线程1接着进行加1操作;
问题1:不是说volatile变量被修改会通知其他线程缓存的变量无效吗?为什么线程2修改之后,线程1中的缓存依然有效?难道线程1不应该再次去主存读取吗?
问题2:有没有可能线程1和2同时对inc做出了修改,当写入主存时,以哪个线程的值为准呢?还是说随机?
问题3:如果说即使线程2对inc做出了修改,线程1依然拿着自己缓存的inc在操作,那还能说volatile变量能保证可见性吗?是否只能说volatile尽量保证最大程度的可见,但不完全保证?
而在Java的官方文档中,也说明了volatile只是reduce the risk of memory consistency error,见http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html。
3.如何保证有序性
1.同样,happends-before保证有序性;
2.synchronized和lock锁也能保证有序性
3.重点来了,volatile能保证有序性吗?
在happens-before的定义中,有这么一条,A write to a volatile
field happens-before every subsequent read of that field.
海子大大的原文中说的是:
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
问题:volatile读操作是否也能禁止处理器对指令进行重排序吗?
Section2 总结
总结:按我的理解,volatile应该能达到两种效果:
1.一定程度上保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他从主存中读取该值的线程来说是可见的。
2.当对volatile变量进行写操作,处理器无法进行重排序。