1. 介绍
1.1 AQS 简要介绍
在 Java 中,AQS(AbstractQueuedSynchronizer
)为开发并发工具提供了方便的 api,它将负责的线程管理封装起来,其子类只需要 规定状态的含义 并 实现需要使用的方法 即可。
1.2 自定义独占锁的简要介绍
本文将会实现一个简单的独占锁,它有 获取锁 和 释放锁 的基础功能,而且是 非公平 的,但不支持以下高级特性:
- 重入:一个线程可以在持有锁的情况下再次获取这个锁。
- 中断响应:当线程在排队等待锁释放时,可以被打断。
它对状态(state
)的定义如下:
- 0:锁处于空闲状态。
- 1:锁已经被某个线程占有。
2. 锁的获取和释放流程
对于 AQS 的 独占模式 而言,锁的获取和释放流程如下所示:
2.1 获取锁
- 子类获取锁的方法中调用
acquire(int arg)
方法尝试获取锁(以下是 AQS 类的acquire
方法内部的流程): - 首先调用
tryAcquire(int arg)
方法尝试直接获取锁(该方法需要由子类实现)。 - 如果
tryAcquire
返回true
,表示获取锁成功,流程结束。 - 如果
tryAcquire
返回false
,表示获取锁失败,将当前线程封装成一个独占模式的节点(即Node.EXCLUSIVE
),并插入到同步队列的尾部。- 线程进入阻塞状态,等待前驱节点释放锁并唤醒自己。
- 当前驱节点释放锁并唤醒自己后,再次尝试获取锁。
- 对于 非公平锁,第二步会 直接调用
tryAcquire
方法抢占锁。- 而对于 公平锁,第二步需要 先调用
hasQueuedPredecessors
方法判断同步队列中是否有正在等待的线程,如果有,则将其封装成节点插入到队列尾部;否则可以直接调用tryAcquire
抢占锁。
2.2 释放锁
- 子类释放锁的方法中调用
release(int arg)
方法尝试释放锁(以下是 AQS 类的release
方法内部的流程): - 首先调用
tryRelease(int arg)
方法尝试直接释放锁(该方法需要由子类实现)。 - 如果
tryRelease
返回true
,表示释放锁成功,从同步队列中唤醒后继节点。 - 如果
tryRelease
返回false
,表示释放锁失败,流程结束。
3. 代码实现
public class SimpleExclusiveLock extends AbstractQueuedSynchronizer {
// 获取锁
public void lock() {
// acquire() 的参数没有在 tryAcquire() 中使用,随便传递一个值
acquire(1);
}
// 释放锁
public void unlock() {
// release() 的参数没有在 tryRelease() 中使用,随便传递一个值
release(1);
}
@Override
protected boolean tryAcquire(int arg) {
// 通过 CAS 操作尝试将状态设置为 1,原始状态为 0,如果失败,则表示锁已被其它线程占有,返回 false
if (!compareAndSetState(0, 1)) {
return false;
}
// 否则表示锁处于空闲状态,将锁的持有者改为当前线程,返回 true
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
@Override
protected boolean tryRelease(int arg) {
// 如果状态等于 0,则表示锁处于空闲状态,这时不应该释放锁,抛出非法监视器状态异常
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
// 否则状态合法,将锁的持有者改为 null,状态设置为 0,返回 true
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
4. 对锁的测试
注:测试时请提前导入 junit-jupiter
依赖,测试时如果没有报错就说明成功了。
public class TestSimpleExclusiveLock {
private int num = 0;
// 测试 lock() 和 unlock()
@Test
public void testLockAndUnLock() throws InterruptedException {
SimpleExclusiveLock lock = new SimpleExclusiveLock();
// 定义一个任务,把 num 自增 10000 次
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
num++;
} finally {
lock.unlock();
}
}
};
// 让两个线程去执行这个任务,并等待这两个线程执行完毕
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
// 断言结果等于 20000
Assertions.assertEquals(20000, num);
}
// 测试单独的 unlock() 是否会抛出 非法监视器状态异常
@Test
public void testOnlyUnlock() {
SimpleExclusiveLock lock = new SimpleExclusiveLock();
// 单独执行 unlock(),断言抛出非法监视器状态异常
Assertions.assertThrows(IllegalMonitorStateException.class, lock::unlock);
}
}
5. 总结
本文通过 AQS 实现了一个简单的非公平独占锁,旨在 了解 AQS 独占模式下锁的 获取 和 释放 流程。对于子类来说,重点在于 state
的定义 和 重写 tryAcquire()
和 tryRelease()
方法。在重写这两个方法的过程中,使用了 AQS 父类提供的如下方法:
compareAndSetState()
:设置前先判断待修改值是否等于预期值,如果等于,则修改成功;否则修改失败。setExclusiveOwnerThread()
:设置锁的独占持有者线程。getState()
:获取state
的值。setState()
:设置state
的值。hasQueuedPredecessors()
:判断同步队列中是否有比当前线程等待时间更久的线程,可用于判断同步队列中是否有正在等待的线程。