《Java并发编程的艺术》之synchronized及JUC

synchrnoized的happens-before

int a;
boolean flag;
public synchronized void init(){// ①
    a = 100; // ②
    flag = true; // ③
}// ④
public synchronized void doTask(){ // ⑤
    if(flag){ // ⑥
        int result = a; // ⑦
    }
} // ⑧

假设线程A执行init(),线程B执行doTask(),有以下的happens-before关系:java

  • 根据程序次序规则:
    • ① hb ②
    • ② hb ③
    • ③ hb ④
    • ⑤ hb ⑥
    • ⑥ hb ⑦
    • ⑦ hb ⑧
  • 根据监视器规则:
    • ① hb ④
    • ④ hb ⑤
    • ⑤ hb ⑧

根据传递规则,保证init()方法全部的修改对doTask()方法可见,happens-before关系以下所示
缓存

一、2间的表示程序次序性规则,四、5间的表示监视器规则,因为三、4有happens-before关系,四、5有happens-before关系,因此根据传递性规则,二、6间有happens-before关系。数据结构

线程A释放锁以前全部可见的共享变量,在线程B获取同个锁以后就变得可见了。app

synchrnoized的内存语义

synchrnoized获取锁内存语义

当释放锁时,JMM会把线程对应的本地内存中的共享变量刷新到主内存。以上面的例子为例,共享数据的状态示意图以下所示
学习

synchrnoized释放锁内存语义

当A线程获取到锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。线程

语义总结

对比锁释放-获取的内存语义和volatile的写-读内存语义能够看出:锁释放与volatile写有相同的内存语义;锁获取和volatile读有相同的内存语义。3d

  • 线程A释放一个锁,实质上是告诉其余 要获取该共享变量 线程 一个消息(线程A所作的修改)
  • 线程B获取一个锁,实质上是接受到其余线程发出的消息(释放这个锁的线程对共享变量所作的修改)
  • 线程A释放,随后线程B获取,实质上是线程A经过主内存给线程B发送消息。

这里判断语义是否相同是经过两个操做的流程是否相同,好比线程A的锁释放完时,刷新至主内存;volatile写完后,刷新至主内存,并通知其余线程本地内存的共享变量失效(在锁释放环节里是交给锁获取执行);code

CAS和JUC

synchronized是经过控制对象头来控制锁的升级,可是具体获取锁和释放锁的流程藏在JVM里,这里将经过ReentrantLock类比synchronized过程。对象

ReentrantLock的实现流程blog

这里要学习的是CAS,JDK文档对该方法的说明以下:若是当前状态值等于预期值,则以原子方式将同步设置为给定的更新值。此操做具备volatile读和写的语义。
前面讲到volatile写保证volatile写不会和前面的操做发生重排序,volatile读保证volatile读不会和后面的操做发生重排序。组合这两个条件就意味着同时实现了 禁止某一操做和操做前、操做后的重排序。CAS操做就是如此,它在是经过加上lock前缀来实现如下的功能:

  • 使用缓存锁定保证原子性
  • 禁止以前和以后的重排序
  • 把写缓冲区中的全部数据刷新到内存

正是由于CAS同时具备volatile读和写的内存语义,所以Java线程之间的通讯有下面四种方式。

  1. A线程写volatile变量,B线程读这个volatile变量
  2. A线程写volatile变量,B线程用CAS修改volatile变量
  3. A线程用CAS修改volatile变量,B线程用CAS修改这个变量
  4. A线程用CAS修改volatile变量,B线程用volatile读取该变量

JUC包的通用化的实现模式:

  • 声明共享变量为volatile
  • 使用CAS的原子条件来实现线程间的同步
  • 配合volatile读/写和CAS 来实习线程间的通讯

AQS,非阻塞数据结构和原子变量类,这些JUC包中的基础都是使用上面的模式来实现的,而JUC包的高层类又是依赖这些基础类来实现的。从总体看,JUC包的实现示意图以下所示。

相关文章
相关标签/搜索