Java高并发编程之volatile关键字

前两篇文章讲了synchronized关键字,详见:
Java高并发编程之synchronized关键字(一)
Java高并发编程之synchronized关键字(二)
依然先从一段简单的代码开始:

public class T011 {
    public boolean running = true;
    public void m() {
        System.out.println("m start...");
        while (running) {

        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
        T011 t011 = new T011();
        new Thread(t011::m, "thread_1").start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        t011.running = false;
    }
}

线程thread_1启动后执行t011对象的m方法,m方法中有一个while循环,当running为true的时候,while循环将持续下去,要让while循环停止,最直观的方法是,将t011的running设置为false,但是,代码运行起来,却发现,m方法一直不会结束,这是为什么呢?
大致上讲,running存在于堆内存的t011对象中,当thread_1线程开始运行的时候,会把running的值从内存中copy到thread_1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取堆内存,这样,当主线程修改running的值之后,thread_1线程感知不到,所以不会停止运行。
那么,有什么办法能让thread_1停止运行呢?答案是使用volatile关键字,将会强制所有线程都去堆内存中读取running的值。
public boolean running = true;改为public volatile boolean running = true;即可达到目的。
总结起来,volatile关键字的作用,是使一个变量在多个线程间可见。
值得注意的是,volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能完全替代synchronized。volatile只保证可见性,而synchronized既保证可见性,又保证原子性。
volatile不能保证原子性,用下面的小程序可以证明:

public class T015 {
    volatile  int count = 0;
    void m() {
        for(int i = 0; i < 1000; i++) {
            count++;
        }
    }

    public static void main(String[] args) {
        T015 t015 = new T015();
        List<Thread> threads = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            threads.add(new Thread(t015::m, "thread_" + i));
        }

        threads.forEach(o->o.start());

        threads.forEach(o->{
            try {
                o.join();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        System.out.println(t015.count);
    }
}

如果volatile可以保证原子性,那么结果应该是10000,然而并不总是得到10000。
要保证原子性的做法很简单,只需要在m方法的声明处加上synchronized即可。
如果不想使用synchronized,又希望得到正确的结果,该怎么做呢?
可以使用AtomicInteger,代码如下:

public class T015 {
    AtomicInteger count = new AtomicInteger(0);
    void m() {
        for(int i = 0; i < 1000; i++) {
            count.incrementAndGet();
        }
    }

    public static void main(String[] args) {
        T015 t015 = new T015();
        List<Thread> threads = new ArrayList<>();
        for(int i = 0; i < 10; i++) {
            threads.add(new Thread(t015::m, "thread_" + i));
        }

        threads.forEach(o->o.start());

        threads.forEach(o->{
            try {
                o.join();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        System.out.println(t015.count);
    }
}

这里需要注意下,尽管AtomicInteger中的方法是具有原子性的,但是,其多个方法同时使用并不会构成原子性,依然需要synchronized来加以保证。

猜你喜欢

转载自blog.csdn.net/weixin_42486373/article/details/83961721