首页 > 代码库 > 为了防止程序重排序,慎用volatile

为了防止程序重排序,慎用volatile

    之前在InfoQ看到一篇关于java重排序的一篇文章,觉得里面有些知识写得太绝对了,于是想通过实际程序来说明一下:

    关于java重排序,这里就不做介绍了,我们知道JVM底层封装了与OS的交互,它内部有自己的一套类似于OS的内存模型,程序重排序的设计思路基本上是来源于OS。下面直接入正题吧!


    我们知道JVM给每个线程分配了自己的内存空间,也就是说在变量存储方面,分为主内存和线程工作内存,也就是说,所有线程共享主内存,每个线程都有自己的工作内存。程序执行的时候是去工作内存里面取值还是去主内存里面取值呢?下面以代码为例:

public class DemoWork {
    
    private boolean stop=false;
    private boolean start=true;
    
    public void workThread() throws InterruptedException{
        Thread workThread=new Thread(new Runnable() {
            private int i=0;
            @Override
            public void run() {
                // TODO Auto-generated method stub
                while(!stop){
                    i++;
                    /*try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }*/
                }
                start=false;
            }
        });
        workThread.start();
        Thread.sleep(1000);
        stop=true;
        
        Thread printThread=new Thread(new Runnable() {
            private int i=0;
            @Override
            public void run() {
                // TODO Auto-generated method stub
                while(stop&&start){
                 System.out.println("stop is:"+stop);
                 try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        });
        printThread.start();
        

    }

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        DemoWork dw=new DemoWork();
        dw.workThread();
    }

}

上面的代码是不会停下来的,但是如果把sleep那段代码的注释去掉程序就能停下来了,这是什么原因呢?我的理解是:因为线程printThread是能正常执行的,所以有两种可能:

  • 线程workThread里面工作线程stop变量值没有收到主存的同步,而它一直取的是自己工作线程里面的stop值

  • 主线程更新stop没有更新主内存,以至于主内存里面保存的stop值一直是false



  以上第二点我觉得是可以排除的,因为线程printThread里面的值stop值是true,所以造成以上情况第一点的可能性大一点,那为什么把workThread里面的睡眠去掉之后程序又能正常退出呢?那就应该是在执行这些语句的时候主内存更新了工作内存的缘故了(执行打印语句也会推出,至于这里面的原因是什么,暂时还没看到相关的资料,可能跟JVM的重排序规则有关系,但是规则到底是怎样的呢?),接下来我们来说说volatile

volatile

  1. (适用于Java所有版本)读和写一个volatile变量有全局的排序。也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。(但是并不保证经常读写volatile作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。

  2. (适用于Java5及其之后的版本)volatile的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁[7]。

也就是说在上面workThread线程sleep代码段注释的情况下,我们可以使用volatile来修饰stop变量,这样的话就能强制workThread线程去主内存里面取stop的值了,但是这样做的话在高并发现会造成性能问题。之前看了很多的开源代码,里面解决以上主内存与工作内存不同步的方式基本上是采用volatile修饰变量解决的。我在想,既然volatile在并发情况下会造成性能问题,在workThread循环快里面执行什么类型的代码快能方便JVM更好的同步主内存跟工作内存的值,那样的话,在高并发下,就能更快的提高程序性能了。

本文出自 “陈砚羲” 博客,请务必保留此出处http://chenyanxi.blog.51cto.com/4599355/1577101

为了防止程序重排序,慎用volatile