Synchronized和Reentrantlock的挂起逻辑
synchronized中有两个核心的结构
- EntryList + cxq:等待拿锁的线程存储位置
- Waitset:被执行wait方法的线程存储位置
流转:
- 线程获取锁资源失败,扔到EntryList + cxq
- 线程持有锁资源,执行了wait方法,扔到WaitSet
- 其他线程执行了notify/notifyAll方法,WaitSet中的线程会扔到EntryList/cxq中
ReetrantLock中的Condition中支持了类似上述的功能
ReetrantLock中有两个核心的结构
- AQS的同步队列:等待拿锁的线程存储位置
- AQS内部的Condition单向链表:被执行await方法的线程存储位置
流转:
- 线程获取锁资源失败,扔到同步队列
- 线程持有锁资源,执行了await方法,扔到Condition单向链表
- 其他线程执行了signal/signalAll方法,WaitSet中的线程会扔到同步队列中
AQS的Condition支持
AQS是JUC包下的一个抽象类,单独聊AQS没什么,但是他是AQS很多JUC包下的工具类的父类
AQS有三个核心点:
- int类型的state属性
- AQS内部的同步队列
- Condition的单向链表
本文主要细看Condition单向链表:
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
/*
Node只要在Condition单向链表中,状态就是上面的-2
waitStatus简写wt
*/
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
/*
单向链表的下一个节点
*/
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
- Condition是基于Node对象组成的单向链表
- 在Condition中,Node状态必须是-2,如果不是-2,就可以从中移除掉了
- Condition的Node是利用nextWaiter属性连接下一个节点
- Condition中还有指向头尾的两个属性,分别是firstWaiter和lastWaiter
Condition的挂起操作流程
当持有lock锁的线程,执行以下4个流程:
- 将当前对象封装成Node对象,加入单向链表中
- 释放锁资源
- 确认当前线程的Node,没有在AQS的同步队列中。如果在,说明执行了signal方法,那个线程已经进入了同步队列。不需要挂起
- 没有在同步队列,直接挂起
Condition的signal唤醒操作流程
- 确保执行signal的线程持有锁资源
- 将第一个Node从单向链表中断开
- 将Node的状态从-2改成0
- 将Node移到同步队列
- 确保Node在同步队列中可以被唤醒。直接唤醒线程和将prev指向的Node状态设置为-1
Condition在await被唤醒后的逻辑
1、确认被唤醒的方式:
- 单纯地被signal方法唤醒
- 被interrupt中断唤醒
- 被signal唤醒后,然后执行了interrupt(保留中断标记位)
2、确保Node在同步队列后,就可以跳出while循环
3、执行acquireQueued方法后,等待获取锁资源
4、在获取锁资源的同时,如果被中断过,需要确认是否保留中断标记位
5、如果是中断唤醒,需要将当前Node断开单向链表连接
6、根据中断模型,执行抛出异常、方法