锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程获取同一个锁的线程发送消息。
一、锁的释放和获取的内存语义
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
对比锁释放-获取的内存语义与volatile写-读的内存语义可以看出:锁释放与volatile写有相同的内存语义;锁获取与volatile读有相同的内存语义。
线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
线程B获取一个锁,实质上是线程B接受了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
线程A释放这个锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。
二、锁内存语义的实现
在ReentrantLock中,调用lock()获取锁;调用unlock()方法释放锁。
ReentrantLock的实现依赖于Java同步器框架AbstractQueueSynchronizer(AQS)。AQS使用一个整型的volatile变量(state)来维护同步状态,这个volatile变量是ReentrantLock内存语义实现的关键。
使用公平锁时,加锁方法lock()调用轨迹如下:
1)ReentrantLock:lock()
2)FairSync:lock();
3)AbstractQueuedSynchronizer:acquire(int arg)
4)ReentrantLock:tryAcquire(int acquires)
解锁unlock()调用轨迹
1)ReentrantLock:unlock
2)AbstractQueueSynchronizer:release(int arg)
3)Sync:tryRelease(int release)
三、concurrent包的实现
Java线程通信有下面4种方式:
1)A 线程写volatile变量,随后B线程读这个volatile变量
2)A线程写volatile变量,随后B线程用CAS更新这个volatile变量
3)A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量
4)A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量
通用化的的实现模式
首先,声明共享变量为volatile
然后,使用CAS的原子条件更新来实现线程之间的同步
同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义实现线程之间的通信。