内存可见性

内存可见性:
先看一段代码:

public class TestDemo {
    static class Counter {
        public int counter = 0;

    }
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1  = new Thread(){
            @Override
            public void run() {
                while(counter.counter == 0) {
                }
                System.out.println("循环结束");
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                Scanner sc = new Scanner(System.in);
                System.out.println("请输入一个整数");
                counter.counter =  sc.nextInt();

            }
        };
        t1.start();
        t2.start();
    }
}

预期效果是:
线程1会先进入循环,线程2会读取一个用户输入的整数
随着用户输入了一个非0的整数之后,此时线程2读取到数字并修改counter的值
然后线程1发现counter已不满足条件,就结束循环.

实际效果是:
线程2输入数据后,线程1循环并未结束

这样的现象背后,其实涉及到了编译器的优化
在线程1的核心代码中,循环其实没干什么,只是在反复快速的执行循环条件中的比较操作

这里的比较会先从内存中读取flag的值到cpu中
然后会在CPU中比较这个值和0的相等关系
虽然读取内存比读取磁盘速度快很多,但是从CPU的寄存器上读取数据的速度要
比内存中数据还是要快很多

正因如此:
编译器判定这个逻辑中循环啥事也没干,只是频繁读取内存,于是编译器就把这个操作给优化了.
第一次把内存数据读取到CPU后,后续读内存并不是真的从内存中读取,而是直接取刚才CPU中读到的数据

但是这里是有问题的:
编译器认为flag没有改动,其实只是在当前线程中没有改动
编译器不能感知到其他线程是否对flag进行了修改,此时进行了误判

注意:编译器的优化,必须保证一个前提,优化后的逻辑和优化前的逻辑是
但是这个地方编译器的错误优化导致程序

这样的优化策略就是"内存可见性"
如果优化生效,内存就是不可见的了(其他线程修改了也看不见)
如果优化不生效,内存才是可见的(其他线程修改了就能看见了)

volatile关键字
作用:保持内存可见性:
禁止编译器进行刚才这种场景的优化(一个线程读,一个线程写,修改对于读线程来说可能没生效)

线程不安全的场景:
一个场景读 一个场景写 用volatile
两个线程写: 加锁解决线程安全问题

加了volatile之后,对这个内存的读取操作肯定是从内存中取,不加volatile的时候,读取操作可能是不从内存读取了而是直接读取CPU上次读到的旧的值.(当前代码会不会触发这样的编译器优化是不确定的)

因此解决方法是保持内存的可见性,只要在counter前加上volatile就可以防止编译器的错误优化
代码:

public class TestDemo {
    static class Counter {
        public volatile int counter = 0;

    }
    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1  = new Thread(){
            @Override
            public void run() {
                while(counter.counter == 0) {
                }
                System.out.println("循环结束");
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                Scanner sc = new Scanner(System.in);
                System.out.println("请输入一个整数");
                counter.counter =  sc.nextInt();

            }
        };
        t1.start();
        t2.start();
    }
}
++count和count++本质上都是分三个步骤
1.在count的值从内存读到CPU中
2.在CPU中把这个数据+1
3.把结果写回原来的内存中

这个过程不论是前置++,后置++都是一样的,只要修改不是原子的,都有类似的问题
	不是原子的:各种 +=, -=, /= ...
				包括+ - ;
				直接赋值操作不一定不是原子的,针对内置类型来说一般是原子的(64位CPU是的,
				如果是针对32位CPU,针对long来赋值,其实是分两步,高32位和低32位是分两次来赋值的.)
				引用类型不一定
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值