内存可见性

一:内存可见性

public class Demo1 {
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {

          Thread t1=new Thread(()->{
              while(count==0){

              }
              System.out.println("t1线程结束");
          });

          Thread t2=new Thread(()->{
              Scanner scanner=new Scanner(System.in);1
              System.out.println("请输入一个整数:");
              count=scanner.nextInt();

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

上述代码预期效果:
t1线程首先进入循环,当用户输入一个非0整数的时候,就会使t1线程退出循环.结束线程.
但t1实际上并没有真正出现退出的情况,这也是"bug",而产生上述现象的原因,就是**“内存可见性”**

  while(count==0){

              }

从指令角度分析这段代码:
(1)load :从内存读取数据到CPU 寄存器中,
(2)cmp:比较,条件成立,继续循环,条件不成立,退出循环.
然而,一个load指令消耗的时间,会比一个cmp指令消耗的时间多得多,执行一次load的时间,等于上万次cmp执行消耗的时间.
同时,JVM发现每次load执行的结果,是一样的(t2线程修改之前),因此,**JVM就把上述load操作优化掉了,只是第一次真正进行load,后续再执行到load,而是直接读取已经load过的寄存器中的值了(读取寄存器的速度远远大于 读取内存的速度).**当t2线程修改count的值,但由于t1线程并没有从内存中重新读取,所以获取不到更新后的值.

1.2:

public class Demo1 {
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {

          Thread t1=new Thread(()->{
              while(count==0){
                  System.out.println("hello ");

              }
              System.out.println("t1线程结束");
          });

          Thread t2=new Thread(()->{
              Scanner scanner=new Scanner(System.in);
              System.out.println("请输入一个整数:");
              count=scanner.nextInt();

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

当我们在

 while(count==0){
                  System.out.println("hello ");

              }

while循环中打印,就会发现代码又和我们预期的效果一样了,这又是为什么???
因为循环体内存在IO操作,而IO操作是从硬盘中获取数据,因此IO操作消耗的时间比load操作消耗的时间更多,并且IO操作是不能被优化掉的.
总结:上述问题本质上是编译器优化引起的(优化是由javac和java配合完成的工作),优化掉load操作之后,使t2线程的修改,没有被t1线程感知到,这就造成了"内存可见性"问题

二:解决内存可见性问题

2.1 volatile关键字

编译器什么时候优化,什么时候不优化,这是一个"玄学问题".
通过volatile关键字,解决优化问题,让编译器不再优化.
当给变量修饰上volatile关键字之后,编译器就知道了,这个变量是"反复无常"的,编译器就不会再进行优化了
volatile 是专门针对内存可见性的场景来解决问题的,告诉编译器不要进行优化操作.

public class Demo1 {
    public  volatile static int count = 0;
    public static void main(String[] args) throws InterruptedException {

          Thread t1=new Thread(()->{
              while(count==0){
              }
              System.out.println("t1线程结束");
          });

          Thread t2=new Thread(()->{
              Scanner scanner=new Scanner(System.in);
              System.out.println("请输入一个整数:");
              count=scanner.nextInt();

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

2.2:synchronized关键字解决内存可见性问题

synchronized关键字,和volatile关键字处理逻辑上是不同的.
引入synchronized关键字,是因为加锁操作本身太重量了,相比load 来说,开销更大,编译器自然就不会对load 优化了(和sleep ,IO操作原理类似).

public class Demo1 {
    public   static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();

          Thread t1=new Thread(()->{

                   while(count==0){
                       synchronized (locker){{
                           
                       }}
                     }

              System.out.println("t1线程结束");
          });

          Thread t2=new Thread(()->{
              Scanner scanner=new Scanner(System.in);
              System.out.println("请输入一个整数:");
              count=scanner.nextInt();

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

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

十一.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值