并发理论基础 - JMM模型和Happens-Before规则

    针对前面提到的多线程情况下,可能会出现的原子性,可见性和有序性问题。既然存在问题,那么则需要程序员在必要时能按需禁用高速缓存编译优化,当然该禁用是打引号的,真实的实现方式是使用内存屏障+CAS机制保证CPU指令的正确执行。Java的解决方案就是JMM(Java Memory Model)模型和六项Happens-Before原则,具体体现在 synchronized、volatile、final关键字上面(至于关键字都做了什么,还有什么没有解决后续再挨个分析)。

一、从并发角度理解JMM

    针对前面提到的CPU高速缓存的模型,为了方便比较,这里再贴一次图,Java语言有自己的解决方案JMM,如图:

 -----------------------------------------------------------------------------------------------------------------------------------------------------------

    针对上图的底层物理(硬件)模型,JMM提供了8种基本操作类型是原子的、不可再分的(除了double、long类型的变量来说,load、store、read、和write操作在某些平台上允许有例外)。

1、clock(锁定)

    作用于主内存的变量,把一个变量标识为一条线程独享的状态

2、unlock(解锁)

    作用于主内存中的变量,把一个标识为某一线程独享状态的进行释放,释放后才可以被其他线程锁定

3、read(读取)

    作用于主内存的变量,把一个主内存中的变量传输到线程的工作内存中,以便后续的load动作使用

4、load(载入)

    作用于工作内存中的变量,把read操作从主内存中获取到的变量值放入工作内存的副本中

5、use(使用)

    作用于工作内存中的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟就遇到一个需要使用变量的值的字节码指令时将会执行该操作

6、assign(赋值)

    作用于工作内存中的变量,把一个从执行引擎接受到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行该操作

7、store(存储)

    作用于工作内存中的变量,把工作内存中的一个变量值传送到主内存中,以便后续的write操作使用

8、write(写入)

    作用于主内存中的变量,把stor操作从工作内存中得到的变量放入主内存的变量中

    八种原子操作(还有更多的规则,在JVM中进行详细分析)中,store、load操作使用内存屏障解决了并发编程的可见性、原子性、有序性;也是synchronized、volatile关键字实现的根本,后续挨个详解。

 

二、Happens-Before原则

public class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }
  public void reader() {
    if (v == true) {
      // 这里x会是多少呢?
    }
  }
}

1、程序的顺序性规则

    在同一线程中对于上面的write方法,x  = 42 Happens-Before于后面的 v = true,比较容易理解。

2、volatile变量规则

    volatile写操作Happen Before于对这个变量的读操作,针对于上面的volatile修饰的变量读操作,在JDK1.5之后JSR-133规范增强了volatile的语义,获取到的值一定的写入后的新值。

3、传递性

    传递性也比较好理解,A Happens-Before B,B Happens-Before于 C,即使是在不同的线程中A一定Happens-Before于C。规则看似比较简单,但是若结果其他的规则,如:

1、程序的顺序性规则:x = 42 Happens-Before于写变量的 v = true

2、volatile变量规则:写变量的 v = true Happens-Before于 读变量 v = true

3、再结合传递性规则,那么 A线程写入 x = 42的值对 B线程对 x的读取可见

    随着JDK1.5对volatile语义的增强,java.util.concurrent(也称JUC)并发包解决了可见性问题,也是该并发包存在的根本。

 

4、管程中锁的规则

    一个锁的解锁Happen Before于加锁操作,管程是synchronized实现的理论基础,后面详解。

synchronized (this) { //此处自动加锁
  // x是共享变量,初始值=10
  if (this.x < 12) {
    this.x = 12; 
  }  
} //此处自动解锁

5、线程start规则

    线程A中启动了线程B后,子线程B能够看到主线程在启动子线程前的操作

Thread B = new Thread(()->{
  // 主线程调用B.start()之前
  // 所有对共享变量的修改,此处皆可见
  // 此例中,var==77
});
// 此处对共享变量var修改
var = 77;
// 主线程启动子线程
B.start();

6、线程join规则

    线程A等待子线程B(即A中调用B的join方法),当子线程B完成后,A中Join后的操作可以看见B线程run方法中共享变量的修改

Thread B = new Thread(()->{
  // 此处对共享变量var修改
  var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程B可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用B.join()之后皆可见
// 此例中,var==66

7、线程interrupted规则

    对线程interrupted方法的调用先行于 中断线程的代码检测到中断时间的发生

8、对象的finalize规则

    一个对象的初始化完成(即构造函数的执行),现行发生于finalize方法的开始。Object中有一个finalize方法,是在JVM对该对象的垃圾进行回收时会回调该方法,我们可以重新该方法打印东西即可被看到。

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值