Java并发编程艺术之Java中的锁
本文章主要介绍Java并发包中与锁相关的API和组件,会从1)使用 、2)实现 两个方面进行介绍 ,下面是主要包含的内容:
- Lock接口
- 队列同步器(AQS)
- 重入锁
- 读写锁
- LockSupport工具
- Condition接口
一、Lock接口
锁的简单介绍: 锁可以控制多个线程访问共享资源的方式,可以防止多个线程同时访问共享资源,
锁的实现方式: 在不同JDK版本,实现锁的方式不同
- JDK5 之前: 通过synchronized 关键字实现锁的功能
- JDK5 之后: 增加并发包(java.util.concurent)中Lock来实现锁功能(synchronized依然可以实现锁的功能)
synchronized和Lock差异性
- synchronized可以隐式的获取和释放锁(简化了同步管理、扩展性比较差), 而Lock需要显示的获取和释放锁
- synchronized不具有中断获取锁、超时获取锁的功能等同步特性,Lock具有锁释放/获取的可操作性, 具有可中断获取锁,具有超时获取锁等同步特性
下面是Lock提供的synchronized关键字不具备的特性:
特性 | 描述 |
---|---|
尝试非阻塞的获取锁 | 当前线程尝试获取锁,如果这一时刻锁没有被其它线程获取到,则成功获取并持有锁 |
能被中断的获取锁 | 与synchronized不同,获取到锁的线程可以响应中断,当获取到锁的线程响应中断时,会抛出中断异常,同时释放锁 |
超时获取锁 | 在指定的截至时间之前获取锁,如果截止时间之前任然没有获取到锁,则返回 |
Lock API
二、队列同步器(AQS)
队列同步器(AbstractQueuedSynchronizer), 是用来构建锁或者其它同步组件的基础框架; 同步器的使用方式是继承,在抽象方法的实现过程中会通过getState()、setState(int newState)、compareAndSetState(int expect, int updateState)方法对同步状态进行修改 。
注:继承同步器(AQS)的子类推荐为同步组件的静态内部类,同步器自身没有实现任何同步接口,仅仅是定义了同步状态获取和释放的若干方法,以便供自定义同步组件使用,同步器即支持独占式的获取同步状态,也支持共享式的获取状态, 比如: ReentrantLock、ReentrantReadWriteLock、CountDownLatch。
同步器(AQS)、自定义组件、自定义锁三者之间的关系 如下图:
代码实现示例如下:
public class ReentrantLock implements Lock, java.io.Serializable { // 自定义锁, 此示例代码是重入锁
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer { //同步组件, 其中Sync的父类 AbstractQueuedSynchronizer 是队列同步器
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {
// ...nonfairTryAcquire 的实现逻辑
}
protected final boolean tryRelease(int releases) {
// ... tryRelease 的实现逻辑
}
protected final boolean isHeldExclusively() {
// ... isHeldExclusively 的实现逻辑
}
final ConditionObject newCondition() {
// ... newCondition 的实现逻辑
}
}
}
- 锁是面向实现者的,它定义了使用者和锁交互的接口,隐藏了实现的细节
- 同步器是面向锁的实现者的,它简化了锁的实现方式,屏蔽了同步状态管理、线程排队、等待/唤醒 等底层操作 。
1. 队列同步器的接口与示例
同步器是通过模板方法来设计的,因此使用者需要继承同步器,并重写指定的方法,随后将同步器组合在同步组件实现中,最后调用使用者重写的模板方法
下面是同步器可重写的方法和同步器提供的模板方法
针对图4同步器提供的模板方法可以分为3类
- 独占式获取和释放同步状态
- 共享式获取和释放同步状态
- 查询同步队列中等待线程的状况
这里以独占锁的示例初步了解同步器的工作原理
独占锁: 同一个时刻只有一个线程获取到锁, 而其它获取锁的线程只能处于同步队列中等待。
下面是代码示例:
public class MutexThread {
//2. 将需要的操作代理到Sync上
private final Sync sync = new Sync();
public void lock(){
sync.acquire(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public boolean unlock() {
return sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked(){
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads() ;
}
public void lockInterrupterly() throws InterruptedException{
sync.acquireInterruptibly(1);
}
//1. 定义自定义组件, 静态内部类
private static class Sync extends AbstractQueuedSynchronizer {
// 同步器是否被当前线程独占
@Override
protected boolean isHeldExclusively() {
return getState() == 1 ;
}
//当state状态为0的时候获取锁
@Override
public boolean tryAcquire(int acquries) {
if(compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false ;
}
//释放锁, 将状态设置为0
@Override
protected boolean tryRelease(int release) {
if(getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
Condition newCondition() {
return new ConditionObject();
}
}
}
针对上面示例代码做简单介绍:
- MutexThread是一个自定义独占锁, 同一时刻只允许一个线程占有锁
- Sync是静态内部类, 它继承了队列同步器(AbstractQueuedSynchronizer), 该内部类实现了独占式获取和释放同步状态
- tryAcquire(int acquires)方法中,如果经过CAS设置成功,会将同步状态设置为1 , 表示获取同步状态成功了
- tryRelease(int releases)方法中,只是将同步状态设置为0,表示释放同步状态成功,如果之前已经释放(getState() == 0)会抛出非法监视状态异常(IllegalMonitorStateException)
- 使用者并不会直接和内部的同步器实现交互,而是通过MutexThread提供的方法,
2. 队列同步器的实现分析
本小节包含的内容如下:
- 同步队列
- 独占式同步状态获取与释放
- 共享式同步状态获取与释放
- 超时获取同步状态
1) 同步队列
同步队列是一个FIFO的双向队列,
1. 当线程获取同步状态资源失败时, 同步器(AQS)会将当前线程构造成一个Node, 并加入同步队列中,
2. 处于同步队列中的线程处于阻塞状态, 在同步状态释放时,会唤醒首节点中的线程,使其再次尝试获取同步状态
阻塞队列中添加的节点(Node)信息定义如下:
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
从节点Node定义包含如下信息:
- 获取同步状态失败线程的引用(volatile Thread thread ;)
- 等待状态(volatile int waitStatus ;)
- 当前线程的前驱节点(volatile Node prev) 和后继节点(volatile Node next)
关于同步队列添加节点\释放节点的操作,可以通过下面的图示进行了解: