锁的释放-获取建立的hanppens-before关系
锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程想获取同一个锁的线程发送消息。
锁的释放和获取的内存语义
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
当线程获取锁是,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取。
锁和volatile的内存语义相同:
- 线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A堆共享变量所做修改的)消息
- 线程B获取锁,实质上是线程B接收之前某个线程发出的(在释放着锁之前对这共享变量所做修改的)消息。
- 线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。
锁内存语义的实现
公平锁在释放锁的最后写volatile变量state,在获取锁时首先读这个volatile变量。根据volatile的hanppens-before规则,释放锁的线程在写voletile变量之前可见的共享变量,在获取锁的线程池读取同一个volatile变量后将立即变得对获取锁的线程可见。
CAS同时具有volatile的读和volatile写的内存语义。
公平锁和非公平锁总结
- 公平锁和非公平锁释放时,最后都要写一个volatile变量state
- 公平锁获取时,首先会去读volatile变量
- 非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和volatile写的内存语义。
锁释放-获取的内存语义的实现至少有下面两种情况
- 利用volatile变量写-读所具有的内存语义
- 利用CAS锁附带的volatile读和写的内存语义
consurrent包的实现
由于Java的CAS同时具有volatile读和volatile写的内存语义,因此Java线程之间的通信现在有了下面4种方式
- A线程写volatile变量,随后B线程读这个volatile变量
- A线程写volatile变量,随后B线程用CAS更新这个volatile变量
- A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量
- A线程用CAS和更新一个volatile变量,随后B线程读这个变量
Java的CAS会使用现在处理器上提供的高效机器级别的原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent爆得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式
- 首先声明共享变量为volatile
- 然后使用CAS的原子条件更新来实现线程之间的同步
- 同时,配合以volatile的读/写和CAS具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类,这些concurrent包中的基础类都是使用这种模式来实现的,而consurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,consurrent包的实现示意图如下: