《Java 并发编程的艺术》实验01 同步组件的开发
同步组件的开发
Lock 🆚 AQS
Lock 相当于提供了一套规范的锁方法接口,锁最终实现由 AQS 负责
graph LR
自定义锁 --implement--> Lock -.依赖-.-> 自定义Sync --extends--> AQS
Lock 接口 | AQS |
---|---|
Lock 接口是Java提供的一个抽象接口,用于实现线程的互斥访问。 | AQS(AbstractQueuedSynchronizer)是Java提供的一个抽象类,用于实现同步器的基本功能。 |
Lock 接口提供了多个实现类,如 ReentrantLock、ReentrantReadWriteLock 等。 | AQS 定义了一套统一的框架和模板方法,用于实现各种同步器。 |
Lock 接口的实现类可以实现公平锁或非公平锁的功能,以及可重入锁的功能。 | AQS 提供了一套底层同步器的实现机制,包括共享状态、等待队列、线程的挂起和唤醒等。 |
Lock 接口通过调用 AQS 的相关方法来实现线程间的互斥访问,如 tryAcquire() 和 tryRelease() 。 |
AQS 提供了 acquire() 和 release() 等模板方法,供 Lock 接口的实现类来实现具体的加锁和释放锁的逻辑。 |
Lock 接口中的方法可以通过 AQS 的同步状态控制线程的阻塞和唤醒,实现线程的等待和通知机制。 | AQS 提供了一种可重入的同步状态管理机制,使得同一个线程可以多次获取同步资源。 |
Lock 接口的实现类可以通过 AQS 的独占模式或共享模式来实现不同的同步操作,如读写锁的实现。 | AQS 提供了允许多个线程同时访问的共享锁和只允许一个线程访问的独占锁的机制。 |
AQS 作为一个桥梁,将 不同组件的接口语义 和 线程访问以及同步状态控制等底层技术 连接起来
graph LR
同步组件的接口语义 --AQS--- 线程访问以及同步状态控制等底层技术
Muxtex(独占式样例)
需求分析
基于 Lock 接口进行二次开发,实现自定义锁——独占锁 Muxtex,该锁特征如下
- 同一时刻只能有一个线程获取到锁,其他希望获取的线程只能位于同步队列中等待
- 只有获取锁的线程释放锁,其他线程才能获取锁
代码实现
//基于 Lock 二次开发的独占锁
//Lock 接口提供一组抽象方法规范,描述了锁应该具有哪些通用方法
public class Mutex implements Lock {
//自定义继承自 AQS 的同步器 Sync,用于支持锁的通用方法
private static class Sync extends AbstractQueuedSynchronizer {
//占用状态查询:独占锁同步状态为 1 时,处于占用状态
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
//独占锁,实现独占获取方法
//锁获取:当同步状态为零时置为1⃣️
@Override
protected boolean tryAcquire(int arg) {
//CAS 同步状态,若状态为 0,则置为 1
if (compareAndSetState(0, 1)) {
//记录当前拿到锁的线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//独占锁:实现独占释放方法
//锁释放:当同步状态非零时置为0⃣️
@Override
protected boolean tryRelease(int arg) {
//同步状态为 0 时解锁则抛出异常
if (getState() == 0) throw new IllegalMonitorStateException();
//重置当前拿到锁的线程
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//等待队列
Condition newCondition() {
return new ConditionObject();
}
}
private final Sync sync = new Sync();
//锁对外提供的通用方法
@Override
public void lock() {
//将操作代理到 Sync 上
sync.acquire(1);
}
@Override
public void unlock() {
//将操作代理到 Sync 上
sync.release(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
//将操作代理到 Sync 上
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
//将操作代理到 Sync 上
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
//将操作代理到 Sync 上
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
//返回一个 Condition,每个 condition 都包含了一个 condition 队列
@Override
public Condition newCondition() {
//将操作代理到 Sync 上
return sync.newCondition();
}
}
TwinsLock(共享式样例)
需求分析
基于 Lock 接口进行二次开发,实现自定义锁——双层重入锁 TwinsLock,该锁特征如下
- 同一时刻最多有两个线程获取到锁,其他希望获取的线程只能位于同步队列中等待
代码实现
//基于 Lock 二次开发的共享锁
//Lock 接口提供一组抽象方法规范,描述了锁应该具有哪些通用方法
public class TwinsLock implements Lock {
//自定义继承自 AQS 的同步器 Sync,用于支持锁的通用方法
private static final class Sync extends AbstractQueuedSynchronizer {
//同步状态(资源)数量初始化
Sync(int count) {
if (count <= 0) throw new IllegalArgumentException("同步状态非负");
setState(count);
}
//共享锁,实现共享获取方法
@Override
protected int tryAcquireShared(int arg) {
for (; ; ) {
//获取当前同步资源数
int current = getState();
//更新同步资源数
int newCount = current - arg;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
//共享锁,实现共享释放方法
@Override
protected boolean tryReleaseShared(int arg) {
for (; ; ) {
//获取当前同步资源数
int current = getState();
//更新同步资源数
int newCount = current + arg;
if (compareAndSetState(current, newCount)) return true;
}
}
}
//TwinsLock:状态(资源)数为 2
private final Sync sync = new Sync(2);
//锁对外提供的通用方法
@Override
public void lock() {
//将操作代理到 Sync 上
sync.tryAcquireShared(1);
}
@Override
public void unlock() {
//将操作代理到 Sync 上
sync.releaseShared(1);
}
...
}
机制总结
自定义锁
- 自定义锁
- 结构
Sync extends AQS
:面向线程访问和同步状态控制
- 功能
- 同步状态管理
- 加锁解锁控制
- 等待队列管理
- 结构
自定义同步组件流程
- 确定访问模式:独占式 or 共享式
- 定义资源数:独占式资源数为 1,共享式资源按需决定
- 组合自定义同步器
Sync
:自定义同步组件通过组合自定义同步器来完成同步功能,一般情况下将自定义同步器作为自定义同步组件的内部类