并发编程专题-03共享模型-JMM内存

1.Java内存模型

JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。

JMM体现在三方面

  • 原子性-保证指令不会受到线程上下文切换的影响(Monitor 主要关注的是访问共享变量时,保证临界区代码的原子性)
  • 可见性-保证指令不会受CPU缓存的影响
  • 有序性-保证指令不会受CPU指令并行优化的影响

2.可见性

2.1循环无法退出

//测试实例,当修改flag值后,线程无法退出
public class TestLock {

    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (flag){
            }
        }, "t1");

        System.out.println();
        t1.start();
        Thread.sleep(1000);
        flag = false;
    }
}

原因分析:初始状态, t 线程刚开始从主内存读取了 flag 的值到工作内存。因为 t 线程要频繁从主内存中读取 flag 的值,JIT 编译器会将 flag 的值缓存至自己工作内存中的高速缓存中, 减少对主存中 flag 的访问,提高效率。1 秒之后,main 线程修改了 flag 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值。

image-20210616151052375.png

解决方法:volatile(易变关键字),它可以用来修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。

2.2 可见性与原子性的区别

可见性保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况。其只能保证看到最新值,不能解决指令交错。

注:

  • synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是 synchronized 是属于重量级操作,性能相对更低
  • 死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到对 flag 变量的修改,原因是在println()方法中存在synchronized保护的同步代码块

3.有序性

3.1 指令重排现象

现象说明:JVM 会在不影响正确性的前提下,可以调整语句的执行顺序

static int i;
static int j;
// 在某个线程内执行如下赋值操作
//先执行 i 还是 先执行 j ,对最终的结果不会产生影响,故两者的顺序可能发生重排
i = ...;
j = ...;

解决方法:volatile 修饰的变量可以禁用指令重排

4.volatile 原理

volatile的底层实现原理是内存屏障

  • 对volatile变量的写指令后会加入写屏障
  • 对volatile变量的读指令前会加入读屏障

4.1 可见性保障

  1. 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor2(I_Result r) {
     num = 2;
     ready = true; // ready 是 volatile 赋值带写屏障
     // 写屏障
}
  1. 读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {
     // 读屏障
     // ready 是 volatile 读取值带读屏障
     if(ready) {
         r.r1 = num + num;
     } else {
        r.r1 = 1;
     }
}

4.2 有序性保障

  1. 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后

  2. 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

注:volatile 不能解决指令交错

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
  • 而有序性的保证也只是保证了本线程内相关代码不被重排序

5 happens-before规则

happens-before 规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结。

  1. 线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见
static int x;
static Object m = new Object();
new Thread(()->{
     synchronized(m) {
        x = 10;
      }
},"t1").start();
new Thread(()->{
     synchronized(m) {
        System.out.println(x);
     }
},"t2").start();
  1. 线程对 volatile 变量的写,对接下来其它线程对该变量的读可见
volatile static int x;
new Thread(()->{
    x = 10;
},"t1").start();
new Thread(()->{
    System.out.println(x);
},"t2").start();
  1. 线程 start 前对变量的写,对该线程开始后对该变量的读可见
static int x;
x = 10;
new Thread(()->{
    System.out.println(x);
},"t2").start();
  1. 线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用 t1.isAlive() 或 t1.join()等待它结束)
static int x;
Thread t1 = new Thread(()->{
    x = 10;
},"t1");
t1.start();
t1.join();
System.out.println(x);
  1. 线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过 t2.interrupted 或 t2.isInterrupted)
static int x;
public static void main(String[] args) {
     Thread t2 = new Thread(()->{
        while(true) {
             if(Thread.currentThread().isInterrupted()) {
                 System.out.println(x);
                 break;
            }
        }
     },"t2");
     t2.start();
     new Thread(()->{
         sleep(1);
         x = 10;
         t2.interrupt();
     },"t1").start();
     while(!t2.isInterrupted()) {
        Thread.yield();
     }
     System.out.println(x);
}
  1. 对变量默认值(0,false,null)的写,对其它线程对该变量的读可见
  2. 具有传递性,如果 x hb-> y 并且 y hb-> z 那么有 x hb-> z ,配合 volatile 的防指令重排
volatile static int x;
static int y;
new Thread(()->{
     y = 10;
     x = 20;
},"t1").start();
new Thread(()->{
     // x=20 对 t2 可见, 同时 y=10 也对 t2 可见
     System.out.println(x);
},"t2").start();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

攻城老湿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值