目录
一、什么是AQS?
全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
用 state 属性来表示资源的状态(分独占模式<如ReentrantLock>和共享模式<如Semaphore/CountDownLatch>),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
getState - 获取 state 状态
setState - 设置 state 状态
compareAndSetState - cas 机制设置 state 状态
独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet
子类主要实现这样一些方法
tryAcquire 独占
tryRelease 独占
tryAcquireShared 共享
tryReleaseShared 共享
isHeldExclusively 该线程是否正在独占资源
接下来,通过自定义同步锁和Reentralock来进一步深入了解AQS到底是怎么实现的
二、自定义的不可重入锁
-
自定义同步器
可以查阅ReentrantLock,内部有一个Sync继承了AbstractQueuedSynchronizer
final class MySync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int acquires) {
//1代表加锁
if (acquires == 1){
//在AQS类中该方法是对状态变量sate赋值
//原子安全,防止多个线程同时占用该锁,
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
return false;
}
@Override
protected boolean tryRelease(int acquires) {
if(acquires == 1) {
if(getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
//因为只有持有锁的线程才会释放锁,所以不需要原子安全考虑
setState(0);
return true;
}
return false;
}
protected Condition newCondition() {
//ConditionObject()是AQS类的一个内部类
return new ConditionObject();
}
@Override//是否持有独占锁
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
-
自定义锁
class MyLock implements Lock {
static MySync sync = new MySync();
@Override
// 尝试,不成功,进入等待队列
public void lock() {
//底层会调用tryacquire(1)和其他一些方法
sync.acquire(1);
}
@Override
// 尝试,不成功,进入等待队列,可打断
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
// 尝试一次,不成功返回,不进入队列
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
// 尝试,不成功,进入等待队列,有时限
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
// 释放锁
public void unlock() {
//底层会调用tryrelease(1)和其他一些方法
sync.release(1);
}
@Override
// 生成条件变量
public Condition newCondition() {
return sync.newCondition();
}
}
可能看完有点懵逼,没事我们再来好好分析下Reentrantlock底层是如何实现的,再回来看会有重新的认识
三、 Reentrantlock原理
-
Reentrantlock的结构图
-
非公平锁加锁
先从看构造器,默认实现的是非公平锁,因为调用的是非公平的同步器,从结构图看出来他继承了Sync的一些方法进行了重写
public ReentrantLock() {
//调用的是非公平的同步器
sync = new NonfairSync();
}
1、当没有竞争的时候
看代码:
先从Reentrantlock的Lock方法开始看
public void lock() {
sync.lock();
}
点击进去找到NonfairSync的lock方法实现
final void lock() {
//首先开始锁的状态是0,当没有竞争的时候(没被其他线程修改过),这个条件会成立
//state变成1
if (compareAndSetState(0, 1))
//将当前线程设置为锁的持有者
setExclusiveOwnerThread(Thread.currentThread());
else
//如果锁的状态不是0了,修改不成功了,说明有竞争,进入这个方法
acquire(1);
}
2、第一个竞争出现时
看代码
出现竞争的情况进入acquire()方法
public final void acquire(int arg) {
//tryAcquire方法暂时不细说,他也是获取锁,在下面的可重入会提到
//线程会再一次尝试获取锁,获取失败的话,返回fales,!fales就是true
if (!tryAcquire(arg) &&
//前面条件是ture,那就会执行这个判断
//两个方法,一个acquireQueued,一个addWaiter
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3、进入同步队列逻辑
先调用了addWaiter方法图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态Node 的创建是懒惰的其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线。head代表双向链表的头,tail代表双向链表的尾部,第一次进入addWaiter方法的时候,首先会先创建一个哨兵,并且将当前线程放到同步队列中,并把当前节点的地址给尾部。
接下来才会将node(Thread-1加入同步队列)
注:同步队列中的单向箭头代表的不是node的指向,而是代表地址相同
看代码:
private Node addWaiter(Node mode) {
//将当前线程封装成为node对象
Node node = new Node(Thread.currentThread(), mode);
//tail是AQS同步队列队尾元素
Node pred = tail;
//如果队尾不是null证明同步队列有元素
if (pred != null) {
node.prev = pred;//node的prev指向队尾
if (compareAndSetTail(pred, node)) {
pred.next = node;//让队尾的next指向它
return node;//返回封装当前线程的node
}
}
enq(node);//如果不存在元素执行初始化,创建哨兵,并将当前节点加入同步队列
return node;//返回该线程节点
}
//上述pre,next不能说明方向
enq方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 如果尾是空的
if (compareAndSetHead(new Node()))//给head初始化一个节点
tail = head;//并让初始化的节点的地址给尾
//创建完哨兵以后再次进入循环,会进入eles,将当前线程封装的节点加入同步队列
} else {
node.prev = t;//让当前节点的pre指向尾
if (compareAndSetTail(t, node)) {//并让当前节点成为尾节点的地址
t.next = node;//尾节点的next指向当前节点
return t;
//else里面的操作,因为头尾都是同一个地址,只需要操作尾即可,就出现上面的图了
}
}
}
}
4、acquireQueued 逻辑
1、acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
2、如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
3、进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false
4. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败
5. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true
6. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)
看代码:
acquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;//最开始打断状态是false
for (;;) {
//获得当前节点的前驱节点,此刻前驱节点就是哨兵(和头元素同地址)
final Node p = node.predecessor();
//判断P是不是等于头节点(哨兵),那么再一次尝试获取锁,这里说明只有是头哨兵后的第一个节点才配尝试
//(这里等看到解锁再回来看)如果被阻塞唤醒以后,又回到了这里
if (p == head && tryAcquire(arg)) {//阻塞唤醒后获取锁成功
setHead(node);//让当前节点变成哨兵
p.next = null; //断掉前哨兵和获取锁成功的线程的联系
failed = false;
return interrupted;// 还是需要获得锁后, 才能返回打断状态
}
//获取失败,或者不是老二进入shouldParkAfterFailedAcquire方法,
//该方如果前驱节点是0会改成-1,返回fales
//如果是前驱节点是-1会直接返回ture
//1、很明显第一次进入,前驱节点是0,改成-1返回fales,再一次进入循环
//2、再一次到达这以后,返回了ture,执行后面的parkAndCheckInterrupt()阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 如果是因为 interrupt 被唤醒才会执行这里, 设置打断状态为 ture
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire 方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//获得前驱节点的waitStatus
if (ws == Node.SIGNAL)//Node.SIGNAL是-1
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//将前趋节点的waitStatus改成-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//park住当前线程
//如果有其他线程来打断(不是unpark是打断),返回ture,并清空打断标记
// 如果打断标记已经是 true, 则 park 会失效,下一次再来park就park不住
//如果不是因为打断而是unpark则返回的是fales
return Thread.interrupted();
}
public final void acquire(int arg) {
if (
!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
) {
// 如果打断状态为 返回的是true,进入selfInterrupt,这就是不可打断模式
//如果是unpack返回的就是fales,不会执行下面的selfInterrupt
selfInterrupt();
}
}
static void selfInterrupt() {
// 重新产生一次中断
Thread.currentThread().interrupt();
}
}
5、再次有多个线程经历上述过程竞争失败,变成这个样子
-
解锁
设置 exclusiveOwnerThread 为 null
state = 0
当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued 流程的parkAndCheckInterrupt方法处
如果加锁成功(没有竞争),会设置
exclusiveOwnerThread 为 Thread-1,state = 1
head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
原本的 head 因为从链表断开,而可被垃圾回收
看代码
先找到unlock方法
public void unlock() {
sync.release(1);
}
看release方法
public final boolean release(int arg) {
if (tryRelease(arg)) {//如果符合解锁条件
Node h = head;
//判断队列是不是不为空
//判断哨兵的waitStatus是不是不等于0
if (h != null && h.waitStatus != 0)
//唤醒哨兵后一个元素
unparkSuccessor(h);
return true;
}
return false;
}
再点进去看tryRelease方法(和公平锁共用)
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//获得当前的状态是1-传入的1,c=0;
//判断当前线程是否是持有锁的线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//如果c=0;
free = true;
setExclusiveOwnerThread(null);//让锁没有持有者
}
setState(c);//设置锁的状态是0
return free;//返回free即接锁成功与否
}
看unparkSuccessor方法
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)//判断哨兵的waitStatus是不是-1
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;//找到哨兵的下一个元素
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)//如果不是空,唤醒它,回到当前线程在锁阻塞的方法上
LockSupport.unpark(s.thread);
}
-
非公平锁的体现
如果此刻在刚好唤醒同步队列的thread1要设置为此刻的锁持有者时(正准备执行tryAcquire方法时)此时来了一个新的线程,把锁抢了,thread1又拿不到锁再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞
-
可重入原理
这是Reentrantlock支持的方式
sync中的方法,非公平锁调用的tryAcqire最终层层调用的就是且只有这个方法
非公平锁调用的tryAcqire最终层层调用的就是且只有这个方法,
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
else if (current == getExclusiveOwnerThread()) {
// state++
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);//设置锁的state数值
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// state--
int c = getState() - releases;
//如果当前线程不是锁持有者抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//如果一直接锁c被剪到0,证明重入的锁被接解除完
free = true;
setExclusiveOwnerThread(null);//将锁的持有者社为空
}
setState(c);//设置当前锁状态
return free;//如果解完,则返回ture,没解完返回fales
}
-
公平锁和非公平锁的区别
//非公平锁的lock是直接不管一进来就先抢锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//公用方法
}
//而公平锁先直接调用的是这个acquire方法
final void lock() {
acquire(1);//共用方法
}
//进入这了以后
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//上面分析可重入原理已经知道非公平锁的tryAcquire方法了
//公平锁的tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// h != t 时表示队列中有 Node
return h != t &&
// (s = h.next) == null 表示队列中还有没有老二
// 或者队列中老二线程不是此线程
((s = h.next) == null || s.thread != Thread.currentThread());
}
//来看看公平锁的tryAcquire方法
总结:
即非公平锁在调用自己的lock的时候,直接先抢锁,而公平锁则把第一次抢锁放在了tryAcquire方法中,并在抢锁前先判断了同步队列里是不是有等待唤醒的线程抢锁
-
条件变量实现原理
1、await()流程
开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部
接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁
unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功
park 阻塞 Thread-0
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//进入addConditionWaiter
Node node = addConditionWaiter();
//将节点线程上的所有锁全部释放掉,因为可能存在锁重入
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
//让后让当前调用await()的线程在Condition队列中等待了
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 创建一个关联当前线程的新 Node, 添加至队列尾部,Node.CONDITION = -2
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//如果是空,将把他作为firstNode
if (t == null)
firstWaiter = node;
else
//加到尾部
t.nextWaiter = node;
lastWaiter = node;
return node;
}
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获得锁的状态
int savedState = getState();
if (release(savedState)) {//一直清除节点锁的State值,让其变成0,然后会唤醒等待队列中的下一个节点
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
2、signal 流程
假设 Thread-1 要来唤醒 Thread-0
进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node
执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的waitStatus 改为 -1
Thread-1 释放锁,进入 unlock 流程
public final void signal() {
//检查调用signal的线程是不是锁的持有者,否则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//找第一个元素
Node first = firstWaiter;
//如果不为空
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//将他断开和Condition队列的联系
first.nextWaiter = null;
//如果转移失败,则继续找下一个节点
//转移失败可能是在Condition队列的元素被打断或者超时
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//设置当前节点的WaitStatus属性
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将断开联系的节点转移到同步队列里,返回的是原最后一个节点
Node p = enq(node);
int ws = p.waitStatus;
//并将返回的节点的WaitStatus属性设置成为-1
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}