内存可见性如何导致线程不安全

除了多个线程同时修改同一个变量会导致线程不安全之外,还有一种情况就是内存可见性,我们首先来看一段代码

public class ThreadDemo14 {
    static class counter {
        public volatile  int flag = 0;
    }

    public static void main(String[] args) {
        counter c = new counter();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while(c.flag==0){

                }
                System.out.println(("结束循环"));
            }
        };
        Thread t2  = new Thread() {
            @Override
            public void run() {
                Scanner scan = new Scanner(System.in);
                System.out.println(("请输入一个整数"));
                c.flag = scan.nextInt();
            }
        };
        t1.start();
        t2.start();
    }

在这段代码中,含有一个自定义类counter,里面有一个变量flag初始化为0。线程t1所做的事就是去循环判断flag是否为0,循环结束的时候会打印一行字符。线程t2所做的事就是去改变flag的值。
由于多个线程之间资源是共享的,所以预期结果应该是t2修改完flag的值之后返回到内存中,同时t1在循环读取flag的值。当t2返回到内存中去之后t1应该会读取到,发现不等于0,应该会退出循环。接下来我们运行程序看看结果
在这里插入图片描述
我们会发现并不是预期那样,而是并没有退出循环。也就是并没有感知到线程t2把flag的值给改变了。这是为什么呢。

这个现象的背后涉及到了我们的编译器对代码的优化,在线程t1中,循环什么也没干,主要就是反复快速的执行flag和0的比较,这个时候的CPU做的工作就是一直从内存中读取flag的值来和0进行比较。编译器判断这个操作好像luan用没有,只是频繁从内存读取数据,就会把从内存读取数据优化为直接读取上次CPU读取到的数据,来加快效率,但是编译器没想到的是此时线程t2已经把flag的值给修改了。导致线程t1中读取到的flag的值一直都是初始的0.所以就一直都退出不了循环。

这样的优化策略如果成功的话就会导致内存不可见-,也是线程不安全中的一种。那么该如何解决这个问题呢,在Java中可以通过关键字volatile来保持内存的可见性,也就是禁止编译器进行这种场景的优化。
在这里插入图片描述
加上这个关键字之后,CPU读取这个变量的时候就会一直都是从内存中读取,进而也就保证了不会出现线程t1感知不到线程t2对其的改变。

需要注意的是volatile只是保证数据的内存可见性,不会保证对该数据操作的原子性,也就是说线程t1和线程2仍然是抢占式执行的,只是说执行的时候读取数据是从内存中读取了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值