zoukankan      html  css  js  c++  java
  • 并发编程(二):全视角解析volatile

    一、目录

      1、引入话题-发散思考

      2、volatile深度解析

      3、解决volatile原子性问题

      4、volatile应用场景

    二、引入话题-发散思考

    public class T1 {
         /*volatile*/ boolean running=true;
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
          System.out.println(Thread.currentThread().getName()</span>+":start!"<span style="color: #000000;">);
           </span><span style="color: #0000ff;">while</span><span style="color: #000000;">(running){
                </span><span style="color: #008000;">/*</span><span style="color: #008000;">try {
                     TimeUnit.MINUTES.sleep(2);
                } catch (Exception e) {
                     e.printStackTrace();
                }</span><span style="color: #008000;">*/</span><span style="color: #000000;">
           }
          System.out.println(Thread.currentThread().getName()</span>+":end!"<span style="color: #000000;">);
     }
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
           T1 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T1();
    
           </span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"t"<span style="color: #000000;">).start();
           </span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
                TimeUnit.SECONDS.sleep(</span>1<span style="color: #000000;">);
           } </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) {
                e.printStackTrace();
           }
           t.running</span>=<span style="color: #0000ff;">false</span><span style="color: #000000;">;
     }
    

    }
    运行结果:
    无volatile:
    t:start!

    有volatile:
    t:start!
    t:end
    !

    含有volatile是期望的结果,那为什么不添加volatile会产生这种情况呢?
    再谈Java内存模型:
         在虚拟机中,堆内存用于存储共享数据(实例对象),堆内存也就是这里说的主内存。
         每个线程将会在堆内存中开辟一块空间叫做线程的工作内存,附带一块缓存区用于存储共享数据副本。那么,共享数据在堆内存当中,线程通信就是通过主内存为中介,线程在本地内存读并且操作完共享变量操作完毕以后,把值写入主内存。
     
    解析无volatile:
    • 根据上述java内存模型可知,最开始running=true在主存中,开启线程A,线程会把主存的running=true复制一份写入工作内存的共享变量副本中。
    • 当我们改变running=false,在主存中已经发生改变。
    • 线程A一直在工作状态,没有空闲时间去知道主存的情况,而是一直在读本地内存的共享变量副本,也就一直running=true,取而代之也会产生上述情况。

    三、volatile深度解析

    那为什么含有volatile就能及时刷新工作内存呢?它有什么作用呢?
    volatile:可见性(一个线程修改共享变量以后,立马会被其他线程知可见)、禁止重排序。
     
    1、什么是可见性?
    虚拟机的happens-before中的volatile规则:volatile变量写操作先于读操作,一个线程去读取volatile变量,另一个线程去写volatile变量,那么volatile变量的写操作优先。
    • 根据上述java内存模型可知,最开始running=true在主存中,开启线程A,线程会把主存的running=true复制一份写入工作内存的共享变量副本中。
    • 当我们改变running=false,在主存已经发生改变。
    • 就在这时,当主存与工作内存发生不一致的时候,工作内存的共享变量会失效,那么工作内存就会去主存刷新一遍共享变量,所以running=false,自然就执行下面的代码啦!

     

    2、什么是禁止重排序?
    先谈有序性:
    int a=1;
    int b =3;
    int c=a*b;
    在虚拟机中,执行上述代码,一定是按照上述顺序执行吗?那可不一定,像a=1,b=3的顺序完全可能先执行b=3,a=1,这被称为重排序;但是c=a*b一定在a=1,b=3后面,这被成为有序性。
     
    再谈重排序:
    //线程1:
    context = loadContext();  //语句1
    inited = true;            //语句2
    

    //线程2:
    while(!inited ){
    sleep()
    }
    doSomethingwithconfig(context);

    假设上述代码在单线程中,谈过重排序不会对代码造成什么影响,但是我们看这一段代码。
    语句1与语句2并没有太多的依赖关系,参考有序性例子,那么他们就可以重排序,那么可能语句2执行先于语句1,假设在线程1中语句2执行完就刷新inited到主存,还没等语句1执行呢?线程2就执行起来,一看inited=true,挑出循环,执行下面的代码,context=null就报出空指针,显然这是不能被虚拟机允许的。
     
    所以,volatile明确规定禁止重排序,意思就是context=loadContext必须先于inited执行。
    在虚拟机中设定了有序性,也就是前面谈到的happens-before原则。如果不满足上该原则的情况下,虚拟机是可以自由的重排序的,下面附录此规则。
     
    3、先行发生原则(happens-before)
      • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
      • 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
      • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
      • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
      • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
      • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
      • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
      • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

    四、解决volatile原子性问题

    1、volatile能解决原子性问题吗?什么是原子性呢,本不想解释,为了读者能够更透彻理解,再解释一下。

    原子性:只有一个线程访问共享数据,也就是当线程A访问一个代码块的时候,其他线程全部堵塞,只有等代码块全部执行完,才能被其他线程访问共享数据。
     
    public class T2 {
         volatile int count=0;
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
           </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;1000;i++<span style="color: #000000;">)
                count</span>++<span style="color: #000000;">;
     }
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
           T2 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T2();
           List</span>&lt;Thread&gt; threads=<span style="color: #0000ff;">new</span> ArrayList&lt;Thread&gt;<span style="color: #000000;">();
    
           </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;10;i++<span style="color: #000000;">){
                threads.add(</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"thread-"+<span style="color: #000000;">i));
           }
    
           threads.forEach((o)</span>-&gt;<span style="color: #000000;">o.start());
    
           </span><span style="color: #008000;">//</span><span style="color: #008000;">等待所有线程都执行完</span>
           threads.forEach((o)-&gt;<span style="color: #000000;">o.yield());
    
           System.out.println(</span>"count:"+<span style="color: #000000;">t.count);
     }
    

    }
    运行结果:
    count:8710 //每次都不一样。

     

     2、为什么加了volatile还是不能得到预期结果呢?因为它只保证了可见性,不能保证原子性。what?

    再回忆java内存模型:

     

    3、那怎么解决呢?

    方式一:synchronized,jvm对synchronized进行了很大的优化,所以效率也没有想象中那么低。

    public class T3 {
         int count=0;
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">synchronized</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
           </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;1000;i++<span style="color: #000000;">)
                count</span>++<span style="color: #000000;">;
     }
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
           T3 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T3();
           List</span>&lt;Thread&gt; threads=<span style="color: #0000ff;">new</span> ArrayList&lt;Thread&gt;<span style="color: #000000;">();
    
           </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;10;i++<span style="color: #000000;">){
                threads.add(</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"thread-"+<span style="color: #000000;">i));
           }
    
           threads.forEach((o)</span>-&gt;<span style="color: #000000;">o.start());
    
           </span><span style="color: #008000;">//</span><span style="color: #008000;">等待所有线程都执行完</span>
           threads.forEach((o)-&gt;<span style="color: #000000;">o.yield());
    
           System.out.println(</span>"count:"+<span style="color: #000000;">t.count);
     }
    

    }

    方式二:ReentrantLock,跟synchronized的作用差不多。

    public class T5 {
         ReentrantLock lock=new ReentrantLock();
         int  count=0;
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
           lock.lock();
           </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;1000;i++<span style="color: #000000;">)
                count</span>++<span style="color: #000000;">;
           lock.unlock();
     }
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
           T4 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T4();
           List</span>&lt;Thread&gt; threads=<span style="color: #0000ff;">new</span> ArrayList&lt;Thread&gt;<span style="color: #000000;">();
    
           </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;10;i++<span style="color: #000000;">){
                threads.add(</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"thread-"+<span style="color: #000000;">i));
           }
    
           threads.forEach((o)</span>-&gt;<span style="color: #000000;">o.start());
    
           </span><span style="color: #008000;">//</span><span style="color: #008000;">等待所有线程都执行完</span>
           threads.forEach((o)-&gt;<span style="color: #000000;">o.yield());
    
           System.out.println(</span>"count:"+<span style="color: #000000;">t.count);
     }
    

    }

    方式三:AtomicInteger原子类
    public class T4 {
         AtomicInteger count=new AtomicInteger(0);
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
           </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;1000;i++<span style="color: #000000;">)
                count.getAndIncrement();
     }
    
     </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
           T4 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T4();
           List</span>&lt;Thread&gt; threads=<span style="color: #0000ff;">new</span> ArrayList&lt;Thread&gt;<span style="color: #000000;">();
    
           </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;10;i++<span style="color: #000000;">){
                threads.add(</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"thread-"+<span style="color: #000000;">i));
           }
    
           threads.forEach((o)</span>-&gt;<span style="color: #000000;">o.start());
    
           </span><span style="color: #008000;">//</span><span style="color: #008000;">等待所有线程都执行完</span>
           threads.forEach((o)-&gt;<span style="color: #000000;">o.yield());
    
           System.out.println(</span>"count:"+<span style="color: #000000;">t.count);
     }
    

    }

    五、volatile应用场景

    说到这里,读者可能就已经懵逼了,这也有问题那也有问题,那我们什么时候用它呢?
    volatile是基于synchronized提出的效率优化手段,但是它是不能代替synchronized的。
     
    状态标记量:
    一般volatile共享变量,不要用于数据计算,最好去标记一些状态值,比如前面说的running=true。
    volatile boolean inited = false;
    //线程1:
    context = loadContext();
    inited = true;
    

    //线程2:
    while(!inited ){
    sleep()
    }
    doSomethingwithconfig(context);

     九、版权声明

      作者:邱勇Aaron

      出处:http://www.cnblogs.com/qiuyong/

      您的支持是对博主深入思考总结的最大鼓励。

      本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,尊重作者的劳动成果。

      参考:深入理解JVM、马士兵并发编程、并发编程实践

         volatile关键字解析:http://www.importnew.com/18126.html

  • 相关阅读:
    编程这几天出现的很2的问题!!!
    2020 8 1 每日总结
    2020 8 6 每日总结
    2020 8 7 读后感
    2020 8 4 每日总结
    2020 7 30 每日总结
    2020 8 3 每日总结
    2020 8 8 每日总结
    2020 8 2 每周总结
    2020 8 5 每日总结
  • 原文地址:https://www.cnblogs.com/qiuyong/p/7071601.html
Copyright © 2011-2022 走看看