AQS简介
AQS 为构建锁和同步器提供了一些通用功能的实现,因此,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock
,Semaphore
,其他的诸如 ReentrantReadWriteLock
,SynchronousQueue
等等皆是基于 AQS 的。
AQS原理
AQS 是一个抽象类,实现了常规 锁 和 同步器 的一般功能,并且借助 AQS 能创建大量功能丰富的同步器。
- 首先具备锁的通用功能:将共享资源分配给当前有效的线程,并将资源设定为锁定状态
- AQS的核心:CLH 线程阻塞等待和被唤醒时锁分配的机制
CLH
是自旋锁的改进,是一种虚拟的双向队列(形式上更接近双向链表),AQS会为每个申请锁的线程创建一个控制块(实际上是一个节点Node
,保存了线程的引用、当前节点状态、前驱节点、后驱节点)CLH
维护了一个共享成员变量state
(volatile)表示当前资源的获锁情况。state
默认为0
,为1
时即说明资源已被抢占,但state
可以>1
,体现为可重入性。
实现类
AQS支持实现独占锁(ReentrantLock
)、共享锁(Semaphore
和CountDownLatch
)和支持共享和独占的(ReentrantReadWriteLock
)。独占锁顾名思义就是只允许一个线程获取锁。
ReentrantLock
:可重入锁。当线程A调用lock()
,会尝试通过tryAcquire()
来获取独占锁,并使state=1
,此时线程A获取锁成功,但如果失败,就会创建线程的Node
节点加入CLH
等待队列,直到其他线程释放锁。ReentrantLock
是典型的可重入锁,但state>1
时也意味着需要释放多次锁才能让锁回到能被其他线程获取的初始状态。CountDownLatch
:倒计时器。CountDownLatch
先让主线程睡眠await()
,让N
个子线程执行任务并使state=N
,每有一个子线程执行完就调用countDown()
方法,通过CAS操作让state--
,当所有子线程都执行完毕,此时state=0
,CountDownLatch
就会调用uppark()
方法唤醒主线程,执行后续操作。Semaphore
:共享锁。允许N个线程来共享由Semaphore管理的共享资源,超过N个则会被阻塞。
什么是公平锁和非公平锁?
公平锁和悲观锁等一样是一种思想,不是具体的实现,公平锁是指由于AQS
的CLH
维护了一个等待资源的队列,最先排队等待的线程能最先获取到锁。
换言之,非公平锁没有按照请求锁的顺序来分配锁,可能是随机分配或就近分配的,性能上更好,上下文切换相对少,但可能会使得某些线程永远得不到锁。
例如ReentrantLock
默认为非公平锁来提升性能,也能设置为公平锁。
ReentrantLock lock = new ReentrantLock(true); ------------------------------------------------------ // 传入一个 boolean 值,true 时为公平锁,false 时为非公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
什么是可重入锁?
已获取锁的线程可以再次获取锁,未获取锁的线程需要等待持有锁的线程完全释放锁的重入次数后才能获取。
如果锁不可重入,已持有锁的线程再次尝试获取锁将造成死锁。
ReentrantLock和synchonized都是可重入锁。
内容参考 JavaGuide