千家信息网

什么是volatile

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,本篇内容主要讲解"什么是volatile",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"什么是volatile"吧!volatile的3个特性:保证了各个线
千家信息网最后更新 2026年10月31日什么是volatile

本篇内容主要讲解"什么是volatile",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"什么是volatile"吧!

volatile的3个特性:

  • 保证了各个线程之间的可见性

  • 不能保证原子性

  • 防止重排序

可见性:

首先,每个线程都有自己的工作内存,除此之外还有一个cpu的主存,工作内存是主存的副本。线程工作的时候,不能直接操作主内存中的值,而是要将主存的值拷贝到自己的工作内存中;在修改变量是,会先在工作内存中修改,随后刷新到主存中。

注意: 什么时候线程需要将主存中的值拷贝到工作内存

  • 线程中释放锁的时

  • 线程切换时

  • CPU有空闲时间时(比如线程休眠时)

假设有一个共享变量flag为false,线程a修改为true后,自己的工作内存修改了,也刷新到了主存。这时候线程b对flag进行对应操作时,是不知道a修改了的,也称a对b不可见。所以我们需要一种机制,在主存的值修改后,及时地通知所有线程,保证它们都可以看到这个变化。

public class ReadWriteDemo {            //对于flag并没有加volatile    public boolean flag = false;    public void change() {        flag = true;        System.out.println("flag has changed:" + flag);    }    public static void main(String[] args) {        ReadWriteDemo readWriteDemo = new ReadWriteDemo();        //创建一个线程,用来修改flag,如上面描述的a线程        new Thread(new Runnable() {            @Override            public void run() {                try {                    Thread.sleep(3000);                    readWriteDemo.change();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }).start();        //主线程,如上面描述的b线程        while(!readWriteDemo.flag) {        }        System.out.println("flag:" + readWriteDemo.flag);    }}

按照分析,没有加volatile的话,主线程(b线程)是看不到子线程(a线程)修改了flag的值。也就是说,在主线程看来,在没有特殊情况下,flag 永远为false, while(!readWriteDemo.flag) {}的判断条件为true,系统不会执行到System.out.println("flag:" + readWriteDemo.flag);

为了避免偶然性,我让程序跑了6分钟。可以看到,子线程确实修改了flag的值,主线程也和我们预期一样,看不到flag的变化,一直在死循环。如果给flag变量加一个volatile呢,预期结果是,子线程修改变量对主线程来说是可见的,主线程会退出循环。

可以看到,都不到一分钟,在子线程修改flag的值后,主线程随即就退出循环,说明立刻感知到了flag变量的变化。

有趣的是什么呢:如果ab两个线程间隔时间不长,当b线程也延迟10s读(不是上面的立刻读),你会发现两个线程之间的修改也是可见的,为什么呢,stakc overflow上有解答,执行该线程的cpu有空闲时,会去主存读取以下共享变量来更新工作内存中的值。更有趣的是,在写这篇文章的时候,cpu及内存是这样的,反而能正常执行,但是能出现问题就能说明volatile的作用。

如何保证可见性:

首先要先讲一下java内存模型,java的的内存模型规定了工作内存与主存之间交互的协议,定义了8中原子操作:

  1. lock:将主内存的变量锁定,为一个线程所独占。

  2. unlock:将lock加的锁定解除,此时其他线程可以有机会访问此变量。

  3. read:将主内存中的变量值读到工作线程中。

  4. load:将read读取到的值保存到工作内存中的变量副本中。

  5. use:将值传递给线程的代码执行引擎。

  6. assign:将执行引擎处理返回的值重新赋值给变量副本。

  7. store:将变量副本的值存储到主内存中。

  8. write:将store存储的值写入到主内存的共享变量中。

我上网查了下资料,也看了不同的博客,有讲到volatile其实在底层就是加了一个lock的前缀指令。lock前缀的指令要干什么上面也有写。如果对带有volatile的变量进行写操作会怎么呢。JVM会像处理器发送一条lock前缀的指令,a线程就锁定主存内的变量,修改后再刷新到主存。b线程同样会锁定主存内的变量,但是会发现主存内的变量和工作内存的值不一样,就会从主存中读取最新的值。从而保证了每个线程都能对变量的改变可见。

原子性:

在编程世界里面,原子性是指不能分割的操作,一个操作要么全部执行,要么全部不执行,是执行的最小单元。

public class TestAutomic {    volatile int num = 0;    void add() {        num++;    }    public static void main(String[] args) throws InterruptedException {        TestAutomic testAutomic = new TestAutomic();        for (int i = 0; i < 1000; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    try {                        Thread.sleep(10);                        testAutomic.add();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }).start();        }        //等待12秒,让子线程全部执行完        Thread.sleep(12000);        System.out.println(testAutomic.num);    }}

**预期现象:**都说不能保证原子性了,所以,应该结果是不等于1000

不同电脑执行的结果不一样,我的是886,可能你们的不是,但是都说明了volatile都无法保证操作的原子性。

为什么不能保证原子性:

这要从num++操作开始讲起,num++操作可以分为三步:

  • 读取i的值,装载进工作内存

  • 对i加1操作

  • 将i的值写回工作内存,刷新到主存中

我们知道线程的执行具有随机性,假设a线程和b线程中的工作内存中都是num=0,a线程先抢了cpu的执行权,在工作内存进行了加1操作,还没刷新到主存中;b线程这时候拿到了cpu的执行权,也加1;接着a线程刷新到主存num=1,而b线程刷新到主存,同样是num=1,但是两次操作后num应该等于2。

解决方案:

  • 使用synchronized关键字

  • 使用原子类

重排序:

对于我们写的程序,cpu会根据如何让程序更高效来对指令经行重排序,什么意思呢

a = 2;b = new B();c = 3;d = new D();

经过优化后,可能真实的指令顺序是:

a = 2;c = 3;b = new B();d = new D();

并不是所有的指令都会重排序,重排序与否全是看能不能使得指令更高效,还有下面一种情况。

a = 2;b = a;

这两行代码无论什么情况下都不会重排序,因为第二条指令是依赖第一条指令的,重排序是建立在排序后最终结果仍然保持不变的基础上。下面将给出volatile防止重排序的例子:

public class TestReorder {    private static int a = 0, b = 0, x = 0, y = 0;    public static void main(String[] args) throws InterruptedException {        while (true) {            a = 0; b = 0; x = 0; y = 0;            //a线程            new Thread(new Runnable() {                @Override                public void run() {                    try {                        Thread.sleep(10);                        a = 1;                        x = b;                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }).start();            //b线程            new Thread(new Runnable() {                @Override                public void run() {                    try {                        Thread.sleep(10);                        b = 1;                        y = a;                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }).start();            //主线程睡100ms,以保证子线程全部执行完            Thread.sleep(100);            System.out.println("a=" + a + ";b=" + b + ";x=" + x + ";y=" + y);        }    }}

还记得上面说过两个线程如果沉睡时间差不多,它们之间是可见

预期结果:

  • 如果先执行完a线程(a = 1, x = b = 0),再执行完b线程(b = 1, y = a = 1),最终结果a = 1; b = 1; x = 0; y = 1

  • 如果先执行完b线程(b = 1, y = a = 0),再执行完a线程(a = 1, x = b = 1),最终结果a = 1; b = 1; x = 1; y = 0

  • 如果执行a线程过程(a = 1),接着执行了b线程(b = 1,y = a = 1)【为什么y = a一定等于1,因为它们两个之间的改变是可见的】,最后执行了a线程(x = b = 1),最终结果a = 1;b = 1; x = 1; y = 1

可以发现除了上面预期的三种情况,还出现了一种a = 1; b = 1; x = 0; y = 0的情况,相信大家也知道了,这种情况就是因为重排序造成的。要么是a线程重排序先执行x = b;再执行a = 1;,要么是b线程重排序先执行了y = a;再执行了b = 1;;要么是两个线程都重排序了。

如果private volatile static int a = 0, b = 0, x = 0, y = 0;加了volatile关键字会怎么样呢?

为了保证正确性,又持续跑了5分钟,可以发现,确实不会再出现x=0;y=0的情况。

如何防止重排序

先来讲讲4个内存屏障的作用

内存屏障作用
StoreStore屏障禁止上面的普通写和下面的的volatile写重排序
StoreLoad屏障禁止上面的volatile写和下面volatile读/写重排序
LoadLoad屏障禁止下面的普通读和上面的volatile读重排序
LoadStore屏障禁止下面的普通写和上面的volatile读重排序

可能看作用比较抽象,直接举例子叭

  • 对于S1; StoreStore; S2,在S2及后续写入操作之前,保证S1的写入操作对其它线程可见。

  • 对于S; StoreLoad; L,在L及后续读/写操作之前,保证S的写入对其它线程可见。

  • 对于L1; LoadLoad; L2,在L2及后续读操作之前,保证L1读取数据完毕。

  • 对于L; LoadStore; S,在S及后续操作之前,保证L读取数据完毕。

那么volatile是如何保证有序性的呢?

  • 在每个volatile写操作前插入StoreStore屏障,每个写操作后面加一个StoreLoad屏障。

  • 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。

举例,有个对volatile变量的写S,有个对volatile变量的读L,会怎么样呢。

  • 对于写:S1; StoreStore; S ;StoreLoad L这样能够把S(对volatile变量保护在中间)防止重排序。

  • 对于读一样的道理:L1; LoadLoad; L ; LoadStore S,一样把volatile变量保护的好好的。

到此,相信大家对"什么是volatile"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

线程 内存 变量 主存 工作 保证 屏障 指令 原子 结果 面的 情况 两个 之间 要么 作用 副本 前缀 时候 时间 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 上海挚品互联网科技有限公司 港股互联网科技股 互联网科技英文 百单互联网科技有限公司 深圳天众互联网科技公司 哈尔滨新华互联网科技 老年人脱离互联网科技的作文 互联网汽车科技小镇 北京大学互联网科技怎么样 互联网科技扶贫时间 互联网科技和信息 青海科技产业互联网发展 济南有互联网科技公司吗 无锡工业互联网科技有限公司 伍佰万互联网科技怎么赚钱 城市宝互联网科技有限公司 互联网科技数字货币 美国五大互联网科技公司市值多少 互联网科技的前景 互联网科技创业有什么好项目 互联网与科技 互联网巨头科技公司 金融科技创新监管互联网金融 苏州互联网科技贷款公司 科技与互联网的利与弊 团飞科技与伯堂互联网服务平台 广州恒生互联网科技有限 天津腾达互联网科技有限公司 伍佰万互联网科技怎么赚钱 富国互联网金融科技混合基金
0