AQS:AbstractQueuedSynchronizer,抽象队列同步器,用来构建锁和同步器。AQS相当于一个模版,而同步器则是AQS的具体应用。
AQS的发展
自旋锁:对一个变量执行CAS操作
尝试获取锁。
——> 缺点:不能做到让权等待,可能出现饥饿问题,某个线程始终拿不到锁。
CLH锁:通过单向队列
管理线程,而不是直接竞争共享变量。
——>缺点:大量自选操作浪费CPU资源。
AQS:自旋+阻塞/唤醒
,获取锁失败,先短暂自旋,如果还是失败,线程就进入阻塞状态。等待双向队列前面的线程释放锁,然后对后面的线程进行唤醒。
自定义同步器
-
自定义的同步器继承
AbstractQueuedSynchronizer
-
重写AQS暴露的模版方法
//独占方式。尝试获取资源,成功则返回true,失败则返回false。 protected boolean tryAcquire(int) //独占方式。尝试释放资源,成功则返回true,失败则返回false。 protected boolean tryRelease(int) //共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 protected int tryAcquireShared(int) //共享方式。尝试释放资源,成功则返回true,失败则返回false。 protected boolean tryReleaseShared(int) //该线程是否正在独占资源。只有用到condition才需要去实现它。 protected boolean isHeldExclusively()
资源共享方式
Exclusive独占:只有一个线程能执行
Share共享:多个线程可同时执行
队列Node的状态含义
状态 | 值 | 含义 |
---|---|---|
cancelled | 1 | 线程取消获取锁。线程在等待获取资源时被中断或超时。 |
init | 0 | 加入队列的新节点的初始状态。 |
signal | -1 | 后继节点需要当前节点唤醒 |
condition | -2 | 节点在等待Condition。当其他线程调用了Condition的signal()方法时,节点会从等待队列转移到同步队列获取锁 |
propagate | -3 | 前继节点不仅会唤醒后继节点,同时会唤醒后继节点的后继节点 |
图解AQS工作原理
因为AQS只是一个模版,没有具体实现,故这里以ReentrantLock为例。
前提:有3个线程尝试获取锁。分别为T1、T2、T3
-
T1获取到锁,T2会进入到等待队列中等待获取锁。在进入队列之前,需要对队列进行初始化。
-
T2尝试获取锁。但锁被T1获取,T2进入等待队列。同时将head节点的状态0更新为
signal
,表示需要对head节点的后继节点进行唤醒。
-
T3尝试获取锁。但锁被T1获取,T3进入等待队列。同时将T2的状态0更新为
signal
,表示需要对T2节点的后继节点T3进行唤醒。 -
此时T1释放锁,唤醒后继节点T2,。T2被唤醒后,会退出等待队列,但不是真正的退出,而是将T2节点变成head节点。
-
T2释放锁,唤醒后继节点T3。T3被唤醒后,退出等待队列,但不是真正的退出,而是将T3节点变成head节点。
本文的图源来自javaGuide