记录一次可见性问题引发的思考

本文探讨了在Java并发编程中volatile关键字的作用。通过一个例子展示了在while循环中,当条件变量不使用volatile时,由于CPU缓存一致性协议(如MESI协议),线程间的数据同步问题。即使没有volatile,程序也可能因为缓存刷新而正确结束。但这种情况并不总是可靠的,volatile关键字可以确保变量在多线程环境中的可见性,避免因缓存导致的不一致。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可见性问题

欢迎大家在评论区说出自己的想法

案例描述:

  • 写一个while循环,条件变量设置为ture,创建一个线程(线程0)调执行此方法。
  • 用main线程修改条件变量,看是否while循环结束了。
  • 如果结束了,证明main线程在工作内存中修改的条件变量被线程0读取到了。
  • 如果没结束,证明main线程在工作内存中修改的条件变量没有被线程0读取到。
public class TestVolatile {
    public static void main(String[] args) {
        PrintString  printString = new PrintString();
        //新建线程调用打印字符串的方法
        new Thread(printString::printStringMethod).start();

        //main线程睡眠1000毫秒
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+"线程sleep 1000.....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("在main线程中修改打印标志");
        printString.setContinuePrint(false);
        //在main修改玩打印标志后,子线程是否结束打印。
    }

    static class PrintString{
        private boolean continuePrint = true;
        public void printStringMethod(){
            System.out.println("打印开始");
            while (continuePrint){
                
            }
            System.out.println("打印结束");
        }
        public void setContinuePrint(boolean continuePrint) {
            this.continuePrint = continuePrint;
        }
    }
}

这种写法的运行结果符合预期:

image-20220407213448271

如果加上volatile关键字:

也符合预期。

image-20220407213537313

问题出现:

但是,如果在while循环里加入其他操作后

static class PrintString{
        private boolean continuePrint = true;
        public void printStringMethod(){
            System.out.println("打印开始");
            while (continuePrint){
                //500ms打印一次
                System.out.println(Thread.currentThread().getName()+"打印中");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("打印结束");
        }
        public void setContinuePrint(boolean continuePrint) {
            this.continuePrint = continuePrint;
        }
    }

在不使用volatile关键字的前提下,程序也结束了

image-20220407213821656

不加volatile关键字的情况下程序依旧结束了原因在于CPU的缓存一致性协议:

CPU的缓存一致性协议

例如 Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的,它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

欢迎大家在评论区说出自己的想法!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

结构化思维wz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值