volatile关键字分析总结

125 阅读2分钟

volatile原理

参考这篇大佬的博客点我

volatile关键字的作用

1.保证变量的可见性

多线程下,被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。网上有这样一段多线程脏读共享变量的代码验证,本文进行了参考点我

public class VolatileTest3 {
    static class Work {
        boolean isShutDown = false;

        void shutdown() {
            isShutDown = true;
            System.out.println("shutdown!");
        }

        void doWork() {
            while (!isShutDown) {
                System.out.println("doWork");
            }
        }
    }

    public static void main(String[] args) {
        Work work = new Work();

        new Thread(work::doWork).start();
        new Thread(work::doWork).start();
        new Thread(work::doWork).start();
        new Thread(work::shutdown).start();
        new Thread(work::doWork).start();
        new Thread(work::doWork).start();
        new Thread(work::doWork).start();
    }
}

运行结果如下:

doWork
doWork
doWork
doWork
doWork
doWork
doWork
doWork
doWork
doWork
doWork
shutdown!
doWork

但是,经过分析发现,此代码不能证明脏读。上面的代码中有1、2、3、4、5、6、7七个线程,其中第四个线程是执行shutDown方法,其余线程全部执行work方法。如果第四个线程在执行shutDown方法时,有某个线程已经进入了work方法的while循环,此时不管isShutDown的值有没有变化,都会输出打印语句,因此上面代码不能很好的验证脏读。稍微改造一下代码:

while (!isShutDown) {
    try {
        Thread.sleep(20L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("doWork"+",isShutDown="+isShutDown);
}

输出结果如下:

shutdown!
doWork,isShutDown=true
doWork,isShutDown=true
doWork,isShutDown=true

2.防止字段的重排序

3.volatile关键字修饰的变量线程不安全

volatile关键字修饰的变量依赖本身的值变化就不能保证线程安全。因此volatile变量不能用作计数器之类的功能。验证如下:

public class VolatileTest2 {

    private static volatile int ticket = 10;
    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 3; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        if (ticket > 0) {
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName() + ":" + ticket--);
                        } else {
                            break;
                        }
                    }
                }
            });
        }
        executorService.shutdown();
    }
}

输出如下:

pool-1-thread-1:10
pool-1-thread-2:9
pool-1-thread-3:8
pool-1-thread-1:7
pool-1-thread-3:6
pool-1-thread-2:5
pool-1-thread-1:4
pool-1-thread-2:2
pool-1-thread-3:3
pool-1-thread-1:1
pool-1-thread-3:-1
pool-1-thread-2:0

从结果中可以看到,结果中出现了-1,如果ticket值大一点,还会出现大量的重复值。结论就是变量即使用volatile修饰了但依旧出现了线程安全问题。