1. 引入
在 Java 5.0 之前,在协调对共享对象的访问时可以使用的机制只有 synchronized 和 volatile。Java 5.0 增加了一种新的机制:ReentrantLock。与之前提到过的机制相反,ReentrantLock 并不是一种替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。
如下给出的 Lock 接口中定义了一组抽象的加锁操作。与内置加锁机制不同的是,Lock 提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。在 Lock 的实现中必须提供与内部锁相同的内存可见性语义,但在加锁语义、调度算法、顺序保证以及性能特性等方面可以有所不同。
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit)
void unlock();
}
ReentrantLock 实现了 Lock 接口,并提供了与 synchronized 相同的互斥性和内存可见性。在获取 ReentrantLock 时,有着与进入同步代码块相同的内存语义,在释放 ReentrantLock 时,同样有着与退出同步代码块相同的内存语义。ReentrantLock 支持在 Lock 接口中定义的所有获取锁模式,并且与 synchronized 相比,它还为处理锁的不可用性问题提供了更高的灵活性。
为什么要创建一种与内置锁如此相似的新加锁机制?在大多数情况下,内置锁都能很好地工作,但在功能上存在一些局限性,例如,无法中断一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限地等待下去。内置锁必须在获取该锁的代码块中释放,这就简化了编码工作,并且与异常处理操作实现了很好的交互,但却无法实现非阻寒结构的加锁规则。这些都是使用 synchronized 的原因,但在某些情况下,一种更灵活的加锁机制通常能提供更好的活跃性或性能。
AQS 是实现 Lock 的基础,两者之间的主要区别在于:
- Lock 面向锁的使用者,它聚焦的问题是使用者如何更好地使用锁处理并发问题,而使用者不需要知道锁的实现细节就可以实现互斥同步;
- AQS 面向锁的开发者,它关注的两个主要问题是同步状态管理以及维护线程的同步等待队列。
2. AQS 特性
Java 并发编程核心在于 java.concurrent.util 包,而 juc 当中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这个行为的抽象就是基于 AbstractQueuedSynchronizer(简称 AQS),AQS 定义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器。
- 阻塞等待队列
- 共享/独占
- 公平/非公平
- 可重入
- 允许中断
(1)除了 Lock 外,Java.concurrent.util 当中同步器的实现如 Latch、Barrier、BlockingQueue 等, 都是基于 AQS 框架实现:
- 一般通过定义内部类 Sync 继承 AQS
- 将同步器所有调用都映射到 Sync 对应的方法
(2)AQS 内部维护属性 volatile int state
- state表示资源的可用状态
- State三种访问方式:getState()、setState()、compareAndSetState()
(3)AQS 定义两种资源共享方式
- Exclusive-独占,只有一个线程能执行,如 ReentrantLock
- Share-共享,多个线程可以同时执行,如 Semaphore/CountDownLatch
(4)AQS 定义两种队列
- 同步等待队列
- 条件等待队列
(5)不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS 已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
- isHeldExclusively():该线程是否正在独占资源。只有用到 Condition 才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回 true,失败则返回 false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回 true,失败则返回 false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0 表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回 true,否则返回false。
(6)同步等待/同步阻塞队列
- 【同步等待队列】AQS 当中的同步等待队列也称 CLH 队列,CLH 队列是 Craig、Landin、Hagersten 三人发明的一种基于双向链表数据结构的队列,是 FIFO 先入先出线程等待队列,Java 中的 CLH 队列是原 CLH 队列的一个变种,线程由原自旋机制改为阻塞机制。
- 【条件等待队列】Condition 是一个多线程间协调通信的工具类,使得某个或者某些线程一起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤醒,从而重新争夺锁。