Java 并发编程·volatile 关键字

作者 : jamin 本文共2309个字,预计阅读时间需要6分钟 发布时间: 2020-10-18 共1114人阅读

volatile 关键字

volatile 关键字使一个变量在多个线程间可见

A、B 线程都有用到一个变量,java 默认是 A 线程缓冲区保留一份 copy,这样如果 B 线程修改了变量,则 A 线程未必知道。使用 volatile 关键字,当 B 线程修改了变量的值,会告知 A 线程缓冲区的变量已过期,A 线程就会刷新缓冲区,从主内存中读到变量的修改值(修改 -> 通知 -> 刷新)。

public class T {

    // 不加 volatile,程序永远不会停下
    /* volatile */ boolean running = true;

    public void m() {
        System.out.println("m start");
        while (running) {
            System.out.println("running");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
        T t = new T();

        /*
          需要注意,如果直接执行 t.m() 而不是另起线程,那么无论是否加 volatile,程序都不会停下,循环判断中的 running 一直为 true
        */
        new Thread(t::m).start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.running = false;
        System.out.println(t.running);
    }

}

volatile 并不能保证多个线程共同修改 running 变量时所带来的不一致问题,因为 volatile 只保证了可见性,并不保证原子性,所以 volatile 并不能替代 synchronized

以下栗子可以看出区别,当不加 synchronized 时,最终结果远小于 100000,其错误原因:假设当前值 count 为 100,两个线程 A、B 同时读到的值都是 100(保证了可见性),这时 A、B 线程都执行了 +1 操作,先后覆盖写入 count 值 101,他们不会去检查 count 是否已经是 101,所以两次 +1 操作只实现了一次效果。

public class T {

    volatile private int count = 0;

    // 不加 synchronized,最终结果远小于 10 * 10000
    public /* synchronized */ void m() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }

    public static void main(String[] args) {
        T t = new T();

        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            threadList.add(new Thread(t::m, "Thread" + i));
        }
        threadList.forEach(Thread::start);
        threadList.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println("count: " + t.count);
    }

}

其实,除了上面 synchronized 解决方法外,java 提供的 AtomicInteger 相关类也可以解决。因为 count.getAndIncrement() 是一个原子操作,而 count++ 不是。

public class T {

    private AtomicInteger count = new AtomicInteger(0);

    public void m() {
        for (int i = 0; i < 10000; i++) {
            count.getAndIncrement();
        }
    }

    public static void main(String[] args) {
        T t = new T();

        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            threadList.add(new Thread(t::m, "Thread" + i));
        }
        threadList.forEach(Thread::start);
        threadList.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println("count: " + t.count.get());
    }

}

不过需要注意的是 AtomicInteger 本身方法是原子性的,但不能保证多个方法的连续调用是原子性的。再举个栗子,我们将上面栗子稍微修改一下,加一个判断,我们预期最后 count 值为 1000,但是实际结果却会大于 1000,因为虽然 count.get()count.getAndIncrement() 都是原子操作,但是两者之间却不是,所以会出现当前 count 值为 999,A 线程 count.get() < 1000 判断为真进入循环后,此时 B 线程执行加一操作后值为 1000,A 线程依旧执行加一操作,导致最终结果偏大。

public void m() {
    for (int i = 0; i < 10000; i++) {
        if (count.get() < 1000) {
            count.getAndIncrement();
        }
    }
}
本站所提供的部分资源来自于网络,版权争议与本站无关,版权归原创者所有!仅限用于学习和研究目的,不得将上述内容资源用于商业或者非法用途,否则,一切后果请用户自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容资源。如果上述内容资对您的版权或者利益造成损害,请提供相应的资质证明,我们将于3个工作日内予以删除。本站不保证所提供下载的资源的准确性、安全性和完整性,源码仅供下载学习之用!如用于商业或者非法用途,与本站无关,一切后果请用户自负!本站也不承担用户因使用这些下载资源对自己和他人造成任何形式的损失或伤害。如有侵权、不妥之处,请联系站长以便删除!
金点网络 » Java 并发编程·volatile 关键字

常见问题FAQ

免费下载或者VIP会员专享资源能否直接商用?
本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。
是否提供免费更新服务?
持续更新,永久免费
是否经过安全检测?
安全无毒,放心食用

提供最优质的资源集合

立即加入 友好社区
×