Java关键字volatile是如何保证内存可见性以及防止指令重排序的?

本文首发地址 https://www.dgjava.com/archives/javavolatile01

volatile在Java并发编程中常用于保持内存可见性和防止指令重排序
这句话很简单,看起来很好理解,但似乎又不好理解,所以我们干脆通过代码来说明。

/**
 * <p>深入理解volatile关键字<p/>
 *
 * @Author: 地瓜(yangxw)
 * @Date: 2020/4/8 12:55 AM
 */
public class VolatileTest {
    final static int MXA = 5;
    static int init_value = 0;

    public static void main(String[] args) {
        new Thread(() -> {
            int local_value = init_value;
            while (local_value < MXA) {
                if (init_value != local_value) {
                    System.out.printf("init_value 更新为[%d]\n", init_value);
                    local_value = init_value;
                }
            }
        }, "reader").start();

        new Thread(() -> {
            int local_value = init_value;
            while (local_value < MXA) {
                ++local_value;
                System.out.printf("init_value 变为[%d]\n", local_value);
                init_value = local_value;
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "update").start();
    }
}

这是一段很简单的代码,main方法中启动两个线程。第一个线程比较 init_valuelocal_value 的值,如果不相等,打印最新的 init_value 的值,并更新。第二个线程则对 local_value 做自加操作,并赋值给 init_value 。理想状态下两个线程同时运行,应该输出类似 :

  • init_value 更新为1
  • init_value 变为1

如此交替递增。然而实际效果如下图:
输出结果

可以看到,第一个线程的输入代码没有如预期的那样实现,那就意味着在第一个线程的while循环中,local_value 首次赋值后,init_value 的值一直没变。实际上真的没有变吗?基于以上代码,我将第一个线程做一个改造,在循环中休眠200ms,并输出最新的local_value和init_value,代码如下:

        new Thread(() -> {
            int local_value = init_value;
            while (local_value < MXA) {
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.printf("local_value=[%d],init_value=[%d]\n", local_value, init_value);
                if (init_value != local_value) {
                    System.out.printf("init_value 更新为[%d]\n", init_value);
                    local_value = init_value;
                }
            }
        }, "reader").start();

实际输出结果为:
输出结果

看到这个结果,似乎有些明了。在第一个线程中,并不是init_value没有发生变化,只是线程执行太快,没能及时读取到init_value的最新值。

在一开始说到过,volatile在Java并发编程中常用于保持内存可见性和防止指令重排序。那我们不妨在init_value前加上volatile关键字,看看是否能在第一个线程中实时读取到init_value在内存中的最新值,我们把休眠的代码去掉,只保正常留输出。代码如下:

public class VolatileTest {
    final static int MXA = 5;
    volatile static int init_value = 0;

    public static void main(String[] args) {
        new Thread(() -> {
            int local_value = init_value;
            while (local_value < MXA) {
                if (init_value != local_value) {
                    System.out.printf("init_value 更新为[%d]\n", init_value);
                    local_value = init_value;
                }
            }
        }, "reader").start();

        new Thread(() -> {
            int local_value = init_value;
            while (local_value < MXA) {
                ++local_value;
                System.out.printf("init_value 变为[%d]\n", local_value);
                init_value = local_value;
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "update").start();
    }
}

再来看看运行结果
运行结果

通过这个图,那么volatile在Java并发编程中常用于保持内存可见性就很好理解了。同时也可以结果,为什么volatile关键字在高并发场景下,为何那么好用了。

源码地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值