二十二、JMM和volatile关键字

  1. JMM

    • JMM是JAVA内存对象模型

    • Java线程有两块存储区域

      • 线程工作内存
      • 主内存
    • 由于线程都有自己内部的独立的工作内存,当多个线程操作一个共享变量时,会存在不一致的问

    • 看看下面的问题代码

      /**
       * 该实例程序的原意是想在主线程中修改了共享变量num的值后 让子线程停止循环
       * 但是子线程会一直循环,不知道什么时候停止
       */
      public class Demo1 {
          private static int num = 0;
          public static void main(String[] args) {
              new Thread(()->{
                  while(num == 0){
                      //在该线程中判断共享变量的值
                      //满足条件时 线程在这里循环
                  }
              }).start();
              try {
                  //主线程等待一秒
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              //在主线程中修改共享变量
              num = 1;
              System.out.println(num);
          }
      }
      

      结果

在这里插入图片描述
原因:

​ 子线程在启动时读取到了num的值为0,

​ 然后主线程把num的值改为了1,

​ 这时子线程中的num值还是为0,对主内存中的变化是不可见的。

解决方法:

​ 用volatile修饰共享变量

private static volatile int num = 0;
  1. 内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

    • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
    • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
    • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
    • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
    • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
    • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
    • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
    • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
  2. JMM对这八种指令的使用,制定了如下规则

    • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
    • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
    • 不允许一个线程将没有assign的数据从工作内存同步回主内存
    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
    • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
    • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
    • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
    • 对一个变量进行unlock操作之前,必须把此变量同步回主内存
  3. volatile关键字

    作用

    • 保证可见性

      当线程更改了共享变量后,这个改变在其他线程中是可见的

      public class Demo1 {
          private static volatile int num = 0;
          public static void main(String[] args) {
              new Thread(()->{
                  while(num == 0){
                      //在该线程中判断共享变量的值
                      //满足条件时 线程在这里循环
                  }
              }).start();
              try {
                  //主线程等待一秒
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              //在主线程中修改共享变量
              num = 1;
              System.out.println(num);
          }
      }
      

      共享变量num在主线程中被改为1后,子线程立马结束。

    • 不保证原子性

      public class Demo2 {
          private static volatile int num = 0;
          public static void main(String[] args) {
              //开启20条线程
              for(int i = 1;i <= 20;i++){
                  new Thread(()->{
                      //每条线程循环1000次
                      for (int j = 1; j <= 1000 ; j++) {
                          //下面这个++的操作 虽然num是volatile变量 也会出现多线程并发问题
                          num++;
                      }
                  }).start();
              }
              //判断当前还有多少线程在执行
              //除开当前main线程和gc线程
              while (Thread.activeCount() > 2){
                  Thread.yield();
              }
              System.out.println(num);
          }
      }
      

      执行结果,(多次执行结果不相同)

      在这里插入图片描述

      解决方法,使用原子类替换

      public class Demo2 {
          private static AtomicInteger num = new AtomicInteger();
          public static void main(String[] args) {
              //开启20条线程
              for(int i = 1;i <= 20;i++){
                  new Thread(()->{
                      //每条线程循环1000次
                      for (int j = 1; j <= 1000 ; j++) {
                          num.getAndIncrement();		//这个是一个CAS操作
                      }
                  }).start();
              }
              //判断当前还有多少线程在执行
              //除开当前main线程和gc线程
              while (Thread.activeCount() > 2){
                  Thread.yield();
              }
              System.out.println(num);
          }
      }
      

      每次的执行结果都是20000

    • 禁止指令重排

      内存屏障原理

      被volatile关键字修饰的变量,在被操作的前后会添加内存屏障。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值