【JAVA多线程】volatile关键字在JAVA多线程编程中的作用

     http://www.cnblogs.com/dolphin0520/p/3920373.html
    看完这位大神的分析,依然有一些问题。
Section1 问题
    多线程编程时,要想保证安全,必须满足三个条件。
    首先复习一下作者提出的三个概念:
1. 原子操作
    oracle 官方文档对atomic access 的定义:
    In programming, an atomic action is one that effectively happens all at once. An atomic action cannot stop in the middle: it either happens completely, or it doesn't happen at all. No side effects of an atomic action are visible until the action is complete.

2.可见性
    我认为原作者提出的可见性即JAVA官方文档提出的memory consistency error
    Memory consistency errors occur when different threads have inconsistent views of what should be the same data.
    举个例子:
    int count = 0;
//Thread 1
    count ++;
//Thread 2
    count --;
线程2不一定能看到线程1 对count的修改,因为当程序运行时,每个线程都会加载一份数据到自己的高速缓存中,并对这些数据进行一些操作,只有当该线程将修改后的数据写入主存,且其他线程从主存中重新load了数据,才能看到数据的修改。这也就是原作者所说的可见性。
3.有序性
    处理器会对没有数据依赖的代码进行重排序,在多线程中可能造成意料之外的后果。
Section2 解决方案
    那么,如何在编程时保证1中所说的三个条件呢?
1.如何保证原子操作?
    1.首先,JAVA语言保证对变量的读取和赋值(将值赋给变量,而不是将变量赋给变量)是原子性操作。
    2.JAVA并发包提供了基本类型的 包装类, java.util.concurrent.atomic,官 方介绍:
     http://docs.oracle.com/javase/7/docs/api/
    3.通过给操作共享变量的代码加上synchronized或者lock锁。
注意:volatile是不能保证原子性的。
2.如何保证可见性?
    1.假如A操作 happens-beforeB操作,则A对变量的修改对B一定是可见的。
    happens-before的定义:
     https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5
    2.使用synchronized和lock锁。
    3.重点来了,volatile能保证可见性吗?在海子大大对volatile是否能保证原子性的描述里,有这么一段:    

        对于可见性,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变量进行写操作,处理器无法进行重排序。


你可能感兴趣的:(JAVA)