JUC-JMM&Volatile

JMM

JMM即Java内存模型(Java memory model),是一个抽象的概念,它描述了一系列的规则或者规范,用来解决多线程的共享变量问题

内存交换操作

参考:www.jianshu.com/p/4cfd09fe2…

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

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操作之前,必须把此变量同步回主内存

NNAOJ.png

问题

当线程B将flag修改为flase并写回主存后,线程A用的还是flag=true。

Volatile

Volatile 是jvm提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

可见性

public class Test {
    private static int num=0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(num==0){};
        }).start();
        TimeUnit.SECONDS.sleep(2);
        num=1;
    }
}
复制代码

上述代码由于num=1并未被线程捕获到,所以程序不会停止

解决这个问题只需加个volatile关键字即可

public class Test {
    private volatile static int num=0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(num==0){};
        }).start();
        TimeUnit.SECONDS.sleep(2);
        num=1;
    }
}
复制代码

不保证原子性

num并非20000;

public class Test {
    private volatile static int num=0;
    public static void add(){
        num++;//不是一个原子性操作
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<20;i++){
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }}).start();
        }

        System.out.println(num);

    }
}

复制代码

除了lock和synchronized还有什么可以保证原子性

// private  static AtomicInteger num=new AtomicInteger(0);

public class Test {
    private  static AtomicInteger num=new AtomicInteger(0);
    public static void add(){
        num.getAndIncrement();
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(num.get()==0){};
        }).start();
        TimeUnit.SECONDS.sleep(2);

        for (int i=0;i<20;i++){
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }}).start();
        }
        TimeUnit.SECONDS.sleep(10);
        System.out.println(num);

    }
}
复制代码

java.util.concurrent.atomic-原子操作类包里面提供了一组原子变量类。
其中private volatile int value;保证了可见性。

指令重排

    在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享