失业找工作中 2021-09-30 14:53 采纳率: 0%
浏览 124
已结题

关于volatile的可见性问题,希望大家能够看完,谢过各位了

今天看到黑马视频讲到的volatile的可见性,颠覆了我之前对volatile的认知
之前认为共享变量不加volatile是这样的

img

img


但是看了视频讲的和上面的并不完全相同

不加volatile

@Slf4j(topic = "d.VolatileForeverLoop")
public class VolatileForeverLoop {
    static boolean stop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true;
            log.debug("stop to true");
        },"t1").start();
        foo();
    }

    private static void foo() {
        int i = 0;
        while (!stop) {
            i++;
        }
        log.debug("stop 循环次数{}", i);
    }
}

运行是无法停止的

线程t1 将stop= true

主线程 从主内存中读取false到本地内存 执行while()循环

主线程 是无法感知到stop=true的

但是再新加一个线程t2

@Slf4j(topic = "d.VolatileForeverLoop")
public class VolatileForeverLoop {
    static boolean stop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true;
            log.debug("stop to true");
        },"t1").start();

        new Thread(() -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true;
            log.debug("{}}",stop);
        },"t2").start();
        foo();
    }

    private static void foo() {
        int i = 0;
        while (!stop) {
            i++;
        }
        log.debug("stop 循环次数{}", i);
    }
}

img

可以看到t2线程能够读到t1将stop改为true的

再加一个t3也一样能够读到,只有主线程读不到true。

这里已经显示了并非t1没有将stop=true写回主内存,因为t2已经读到了,但是主线程没有读到

原因

img

CPU-0读取物理内存的stop值为false,那么线程1的while条件满足进入下一次循环,一直读取false一直循环,这样while循环读取的次数是非常多的,

正常编译字节码使用的是解释器,

当循环到达一定次数 ,此时JIT编译器对代码进行了优化,对于热点的代码(频繁调用,反复执行)直接将false替换stop,并将stop进行备份

导致线程2给stop赋值改成true,写回主内存,线程也1无法感知到stop的变化

img

证明
VM配置参数-Xint

表示只用解释器进行编译,不用JIT优化

运行结果如下,此时主线程能够读到stop = true ,100millis循环了600W次

13:39:42 [t1] d.VolatileForeverLoop - stop to true
13:39:42 [main] d.VolatileForeverLoop - stop 循环次数6495969
13:39:42 [t2] d.VolatileForeverLoop - true}

还有一种情况,当t1线程执行的时间非常短,不够使JIT对代码进行优化

将VM配置去除,将t1睡眠时间改为1millis

@Slf4j(topic = "d.VolatileForeverLoop")
public class VolatileForeverLoop {
    static boolean stop = false;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true;
            log.debug("stop to true");
        }, "t1").start();

        new Thread(() -> {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true;
            log.debug("{}}", stop);
        }, "t2").start();
        foo();
    }


    private static void foo() {
        int i = 0;
        while (!stop) {
            i++;
        }
        log.debug("stop 循环次数{}", i);
    }
}


运行结果

13:44:15 [t1] d.VolatileForeverLoop - stop to true
13:44:15 [main] d.VolatileForeverLoop - stop 循环次数82643
13:44:15 [t2] d.VolatileForeverLoop - true}

1millis循环8W次,循环次数过小,JIT认为不属于热点代码,并没有对代码进行优化,

主线程能够感知到stop的变化

第一 -Xint只是用解释器,禁用JIT编译器

第二修改变量执行的时间非常短只有1millis级别

这两种情况都可以使主线程感知到变量的变化

首先执行时间是不可控的,

其次禁用编译器会导致整体性能变低(JIT会有多级优化),JIT编译器能够提升数十倍的编译效率,不能因为一个变量而去降低整体的性能。

所以最终的解决方案还是使用volatile

而加入volatile 是为了告诉JIT不要对这个变量进行优化

从这个原因到证明已经很好地说明了是JIT搞得鬼,
之前了解的例子都是睡眠几百毫秒甚至几秒的,让我以为是修改值后不会立即从本地内存写回主内存
但是试过睡眠1毫秒甚至用睡眠,竟然能够读到修改的值 ,
这和没有加入volatile,修改值后不会立即从本地内存写回主内存,然后加了volatile就会修改后立即刷回主内存貌似没关系,

没有加入volatile应该也会从本地内存写回主内存(可能有所谓的不会立即会写),但是应该是很快的
我想知道各位的看法,如果有误的地方,请不吝赐教,谢过各位了

  • 写回答

1条回答 默认 最新

  • yue_hu 2021-09-30 16:31
    关注

    看完了,感谢你的分享。不过我觉得你好像把volatile的两个特性混到一起了,volatile关键字有两个特性:变量可见和禁止重排序,最开始你截的图说的是变量可见性,你代码验证则是禁止重排序,所以才会觉得不一样。

    评论

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 10月1日
  • 修改了问题 9月30日
  • 创建了问题 9月30日

悬赏问题

  • ¥15 目详情-五一模拟赛详情页
  • ¥15 有了解d3和topogram.js库的吗?有偿请教
  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 unity第一人称射击小游戏,有demo,在原脚本的基础上进行修改以达到要求
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看
  • ¥15 关于#Java#的问题,如何解决?
  • ¥15 加热介质是液体,换热器壳侧导热系数和总的导热系数怎么算
  • ¥100 嵌入式系统基于PIC16F882和热敏电阻的数字温度计
  • ¥15 cmd cl 0x000007b