本章主要介绍Java并发包(concurrent)下与锁相关的API和组件的使用及实现细节。
Lock接口
- 锁:用来控制多个线程访问共享资源的方式,通常一个锁能防止多个线程同时访问共享资源,但读写锁可以允许多个线程并发地访问共享资源。
- JDK1.5前用synchronized实现锁功能,之后Lock提供了与synchronized类似的同步功能,尽管使用时需要显示地释放与获取锁,但与其同时也新增了获取锁与释放锁的可操作性、可中断地获取锁与超时获取锁等特性。
- Lock的使用方式:
1
2
3
4
5
6
7
8
9// 实例化锁对象
Lock lock = new ReentrantLock();
// 获取锁
lock.lock();
try{
}finally{
// 释放锁
lock.unlock();
}
注意:不要在try块中获取锁,因为如果获取锁时发生了异常,抛出异常时,会导致锁无故释放。
- Lock接口比synchronized多的特性,见下表
| 特性 | 描述 |
|---|---|
| 尝试非阻塞地获取锁 | 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁 |
| 能中断地获取锁 | 获取锁的同时可以响应中断,此时抛出中断异常,同时释放锁 |
| 超时获取锁 | 在指定的截止时间内获取锁,如果超时了无法获取锁,则返回 |
Lock接口实现基本都是通过聚合了一个同步器的子类来实现访问控制的
队列同步器AbstractQueueSynchronizer
- 作用:构建锁或其他同步组件的基础框架,其用一个into变量表示同步状态,通过内置的FIFO队列完成资源获取线程的排队工作。
- 同步器的主要实现方法为继承,一般推荐子类定义为自定义同步组件的静态内部类。
- 队列同步器本身没有任何同步接口,仅定义了若干同步状态获取与释放的方法供自定义同步组件使用;
- 队列同步器既可以支持独占式获取同步状态,也可以支持共享式获取同步状态;
- 同步器是实现锁的关键,实现锁的过程中聚合同步器,利用同步器实现锁的语义。同步器与锁的关系: ①锁面向使用者,定义了使用者与锁交互的接口,隐藏了实现细节;②同步器面向锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程排队、等待与唤醒等底层操作。③锁与同步器隔离了使用者与实现者所要关注的领域。
队列同步器的接口与示例
前面说道队列同步器的使用方式为继承,也就是使用者需要重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,这些模板方法会调用使用者重写的方法。
- 有3个方法可以用来访问或修改同步状态:
- getState():获取同步状态
- setState(int newState):设定当前同步状态
- compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法可以保证设置的原子性
- 队列同步器提供的模板方法主要分为3类
- 独占式获取与释放同步状态
- 共享式获取与释放同步状态
- 查询同步队列中的等待线程情况
队列同步器的实现分析
同步队列
- 实现原理
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。 - 节点是构成同步队列的基础,其基本结构如下图所是:

其中同步器将节点加入到同步队列的过程如下图所示:
独占式同步状态获取与释放
- 同步器的acquire(int arg)方法可以获取同步状态,该方法对中断不敏感(线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出);
- 在acquireQueued(final Node node, int arg)方法中,当前线程在“死循环”中尝试获取同步状态,而只有前驱节点是头节点才能够尝试获取同步状态,这样做的原因有二:
① 头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态之后,将唤醒其后继节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点。
② 维护同步队列的FIFO原则。 - 独占式同步状态获取流程,也就是acquire(int arg)方法调用流程,如下图所示:

共享式同步状态获取与释放
独占式超时获取同步状态
重入锁ReentrantLock
名词解释:是支持重进入的锁,它表示该锁能够支持一个线程对
资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择。