文章目录
- 一、AQS是什么
- 二、AQS具备哪些特性
- 三、用的哪种设计模式
- 四、AQS与锁二者之间的关系
- 五、如何基于AQS实现一把独占锁
- 六、参考资料
一、AQS是什么
AQS的全称是 (AbstractQueuedSynchronizer ),它定义了一套多线程访问共享资源的同步器框架,是 J.U.C 包中多个组件的底层实现,如 Lock、CountDownLatch、Semaphore 等都用到了 AQS。
通过查看AbstractQueuedSynchronizer的实现类如下图:
二、AQS具备哪些特性
- 阻塞等待队列
1.1 获取锁失败的线程会进入到一个阻塞等待队列中。- 共享 / 独占
从本质上来说,AQS 提供了两种锁机制,分别是独占锁,和共享锁。
2.1 独占锁,就是存在多线程竞争同一共享资源时,同一时刻只允许一个线程访问该共享资源,也就是多个线程中只能有一个线程获得锁资源,比如 Lock 中的ReentrantLock 重入锁实现就是用到了 AQS 中的排它锁功能。
2.2 共享锁也称为读锁,就是在同一时刻允许多个线程同时获得锁资源,比如CountDownLatch 和 Semaphore 都是用到了 AQS 中的共享锁功能。- 公平/非公平
3.1 公平:线程在获取锁失败时,直接进入阻塞队列。
3.2 非公平:线程在获取锁失败时,进入阻塞队列总会再尝试一次获取锁,插队。- 可重入
4.1 对于同一把锁,获取锁的当前线程可以重复获取。- 允许中断
5.1 提供中断机制,来干预线程之间的通信或协作。
三、用的哪种设计模式
模板方法模式
四、AQS与锁二者之间的关系
- 锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节。
- 同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。
- 锁和同步器很好地隔离了使用者和实现者所需关注的领域。
看两张图吧:
以独占锁ReentrantLock为例:
ReentrantLock实现了Lock的接口,没有直接与AQS交互,而是通过一个内部Sync类继承AQS,将同步器的所有调用都映射到对应的Sync对应的方法。
ReentrantLock加锁方法 lock():
查看 Sync的类图继承关系如下:
五、如何基于AQS实现一把独占锁
- 因为AQS内部帮我们把像多线程访问共享资源获取锁失败时,线程 入队、出队、阻塞、唤醒 提供好了,所以实现一把锁非常容易。
- 我们只需要关注加锁方法 tryAcquire 和释放锁资源方法 tryRelease 即可,其他的AQS已经为我们实现好了。
代码如下:
/**
* Created with IntelliJ IDEA.
* @Author: zhaoxn
* @Date: 2022/11/25/21:51
* @Description:基于AQS实现一把互斥锁
*/
public class MutexLock extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int unused) {
//cas 加锁
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int unused) {
//释放锁
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//调用AQS内部的tryAcquire方法获取锁,失败需要入队或成功直接返回
public void lock() {
acquire(1);
}
//调用自己重写的tryAcquire方法获取锁,失败或成功直接返回
public boolean tryLock() {
return tryAcquire(1);
}
//重写释放锁资源的方法,至于释放锁以后唤醒队列中阻塞的线程,交给AQS内部就好了
public void unlock() {
release(1);
}
}
六、参考资料
Java并发编程的艺术