AbstractQueuedSynchronizer(AQS),是阻塞式锁和同步器工具的框架。本文将初步介绍Java中AQS的基本原理,并基于AQS实现自定义阻塞式不可重入锁,以此来演示AQS的使用。下期会以 ReentrantLock 为例,从源码的层面介绍 AQS 的核心实现 acquire() 方法。
AQS 的目标
- 提供阻塞式获取锁 acquire() 和非阻塞式尝试获取锁 tryAcquire();
- 提供获取锁超时机制;
- 通过打断取消机制;
- 独占机制与共享机制;
- 条件不满足时的等待机制;
AQS 的设计
使用 state 表示资源状态
- 使用 volatile 配合 cas 保证其修改时的原子性;
- 使用 32bit int 来维护同步状态。
使用先进先出队列
- 借鉴 CLH 队列:无锁,使用自旋;快速,无阻塞;
- 头节点 head 不存储数据,尾节点 指向队列最后一个等待线程。
使用 park & unpark 来调度线程
- 可以先 unpark() 再 park();
- unpark & park 针对线程,而不是同步器,控制粒度更为精细;
- park() 可通过 interrupt() 打断。
使用条件变量实现等待、唤醒机制
- 每个条件变量维护一个条件变量队列;
- 线程等待:先将线程从等待队列中移除,再将线程加入条件变量队列;
- 线程唤醒:先将线程从条件变量队列中移除,再将线程加入等待队列。
等待队列和条件变量队列的维护已由 AQS 实现,AQS 的子类只需定义如何维护 state、如何获取和释放锁,主要需要实现的方法如下
- tryAcquire():尝试获取独占锁;
- tryRelease():尝试释放独占锁;
- tryAcquireShared():尝试获取共享锁;
- tryReleaseShared():尝试释放共享锁;
- isHeldExclusively():判断当前线程是否持有锁。
AQS 分为独占模式和共享模式,独占模式同时只允许一个线程访问资源,共享模式同时允许多个线程访问资源。
使用 AQS 的主要并发工具类如下图所示,主要包括:ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore。
自定义阻塞式不可重入锁
自定义同步器
自定义独占式同步器需要重写 AQS 的如下方法:
- tryAcquire():尝试获取锁,约定 state 为 0 表示未加锁,为 1 表示已加锁。
- tryRelease():尝试释放锁
- isHeldExclusively():判断当前线程是否持有锁
- newCondition():创建条件变量
static class ISync extends AbstractQueuedSynchronizer {
/**
* 尝试获取锁
* @param arg 忽略
*/
@Override
protected boolean tryAcquire(int arg) {
if (
// 将state的值置为1
compareAndSetState(0, 1)
) {
// 设置owner为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 尝试释放锁
* @param arg 忽略
*/
@Override
protected boolean tryRelease(int arg) {
// 持有锁的线程才可以释放锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 注意setExclusiveOwnerThread和setState的执行顺序
setExclusiveOwnerThread(null);
setState(0);
return true;
}
/**
* 是否持有锁
*/
@Override
protected boolean isHeldExclusively() {
// 判断锁的持有者是否为当前线程
return getExclusiveOwnerThread() == Thread.currentThread();
}
/**
* 创建条件变量
*/
public Condition newCondition() {
return new ConditionObject();
}
}
在 tryRelease() 方法中,注意 setExclusiveOwnerThread() 和 setState() 的执行顺序,state 被 volatile 修饰,而 exclusiveOwnerThread 没有,因此,后设置 state 可保证 exclusiveOwnerThread 的设置对其他线程可见。
private transient Thread exclusiveOwnerThread;
private volatile int state;
自定锁
使用自定义同步器实现自定义阻塞式不可重入锁。
class ILock implements Lock {
// 自定义同步器
private ISync sync = new ISync();
/**
* 加锁
* 加锁失败则进入队列等待
*/
@Override
public void lock() {
sync.acquire(1);
}
/**
* 加锁
* 加锁失败则进入队列等待,在加锁的过程中可被打断
*/
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
/**
* 尝试加锁
* 加锁失败则返回false
*/
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
/**
* 尝试加锁
* 指定时间内加锁失败则返回false
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
/**
* 解锁
*/
@Override
public void unlock() {
sync.release(1);
}
/**
* 创建条件变量
*/
@Override
@NonNull
public Condition newCondition() {
return sync.newCondition();
}
}
自定义锁中使用到的 AQS 中的方法
public abstract class AbstractQueuedSynchronizer {
// 尝试获取锁
public final void acquire(int arg) {
// 获取锁失败
if (!tryAcquire(arg))
// 加入阻塞队列
acquire(null, arg, false, false, false, 0L);
}
// 尝试获取锁(可打断)
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (
// 线程被打断
Thread.interrupted() ||
(
// 获取锁失败
!tryAcquire(arg) &&
// 加入阻塞队列的过程中出现问题并成功取消获取锁
acquire(null, arg, false, true, false, 0L) < 0)
)
throw new InterruptedException();
}
// 释放锁
public final boolean release(int arg) {
// 释放锁成功
if (tryRelease(arg)) {
// 唤醒阻塞的线程
signalNext(head);
return true;
}
return false;
}
}
END
文章文档:公众号 字节幺零二四
回复关键字可获取本文文档。
如果觉得本文对您有一点点帮助,欢迎点赞、转发加关注,这会对我有非常大的帮助,咱们下期见!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。