在java.util.concurrent包(下称j.u.c包)中,大部分的同步器(例如锁,屏障等等)都是基于AbstractQueuedSynchronizer(下称AQS类)这个类来构建的;它是个抽象的FIFO队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架。
J.U.C包下的同步器的实现都主要依赖于以下几个功能:
- 内部同步状态的管理
- 同步状态的更新和检查操作
- 且至少有一个方法会导致调用线程在同步状态被获取时阻塞,以及在
其他线程改变这个同步状态时解除线程的阻塞
而AQS就实现了以上功能,供其他同步器使用。
所有同步器都有两个基本方法,acquire,release。acquire操作阻塞调用的线程,直到或除非同步状态允许其继续执行。而release操作则是通过某种方式改变同步状态,使得一或多个被acquire阻塞的线程继续执行。(不用同步器命名不同Lock.lock,Semaphore.acquire,CountDownLatch.await...)
之前提过Synchronized内置锁,JVM对其进行了许多优化,其性能已经比ReentrentLock更好,但是常规的JVM锁优化策略并不适用于严重依赖于J.U.C包的典型多线程服务端应用。
大部分情况下,特别在同步器有竞争的情况下,稳定地保证其效率才是J.U.C包的主要目标。
同步器的acquire与release
acquire
while(同步状态不允许acquire){
放入队列 if 没有进队;
依具体需求来决定是否阻塞当前线程;
}
出队 if 已入对;
release
更新同步状态;
if(状态允许被阻塞线程acquire){
解除一个或多个队列里的阻塞线程;
}
要实现上述功能需要三个基本组建的相互协作:
- 同步状态的原子性管理
- 线程的阻塞与解除阻塞
- 队列的管理
同步状态
AQS用单个32位int值 state 来表示同步状态
阻塞
利用LockSupport来阻塞/唤醒线程
队列:
无法获取执行资格的线程会构建一个节点Node加入队列,AQS中使用的是CLH队列:CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒,使其再次尝试获取同步状态。对于队列中的某个节点来说,它只需要通过判断其前一个节点的状态信息来
关于队列的节点Node:5条属性,分别是wateStatus 、prev、next、thread、nextWater。
1,其中 prev,next用于构建双向链表,thread 指向节点对应的线程。
2,waitStatus表示当前被封装成Node结点的等待状态,共有4种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE。
CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,进入该状态后的结点将会被删除。
SIGNAL:值为-1,表明该节点之后有节点在阻塞,当该节点被唤醒或删除后会必须唤醒其后继节点