1. AQS
2.Object的监视器模型(Synchronization应该是这么玩的)
3. 同步队列
3.1 同步器队列基本构造(一个同步队列 + 多个等待队列)
TODO:多个等待队列的话,节点怎么选择其中一个等待队列加入呢
EXPLAIN TODO:
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。
JUC包提供了Condition来对锁进行精准控制,Condition是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创
- await,把当前线程阻塞挂起;
- signal,唤醒阻塞的线程
3.2 Node节点属性如下
static final class Node {
//表示节点的状态,包含SIGNAL、CANCELLED、CONDITION、PROPAGATE、INITIAL
volatile int waitStatus;
//前继节点
volatile Node prev;
//后继节点
volatile Node next;
//当前线程
volatile Thread thread;
//存储在condition队列中的后继节点
Node nextWaiter;
}
waitStatus节点的几种状态:
- CANCELLED,值为1,由于在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待,节点进入该状态将不会变化;
- SIGNAL,值为-1,后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点得以运行;
- CONDITION,值为-2,节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中;
- PROPAGATE,值为-3,表示下一次共享式同步状态获取将会无条件地被传播下去;
- INITAL,值为0,初始状态;
3.3 同步队列如何保证线程安全
- CAS设置首节点和尾节点
- 节点自旋获取同步状态
TODO: 待敲打
3.3 Lock获取锁流程
在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头节点的后继节点。
上图画的有点问题:
等待状态是否大于0 -> 移除该前驱节点时,边的关系是 “是”, 而不是“否”
TODO:等待状态如果是Node.SIGNAL, 为什么直接阻塞了
4. 等待队列
4.1 等待
调用condition的await方法,将会使当前线程进入等待队列并释放锁(先加入等待队列再释放锁),同时线程状态转为等待状态。
从同步队列和阻塞队列的角度看,调用await方法时,相当于同步队列的首节点移到condition的等待队列中
调用condition的signal方法时,将会把等待队列的首节点移到同步队列的尾部,然后唤醒该节点。被唤醒,并不代表就会从await方法返回,也不代表该节点的线程能获取到锁,它一样需要加入到锁的竞争acquireQueued方法中去,只有成功竞争到锁,才能从await方法返回。
参考资料:
【Java 中队列同步器 AQS(AbstractQueuedSynchronizer)实现原理】
【Lock原理分析】