AbstractQueuedSynchronizer(AQS),即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),它是JUC并发包中的核心基础组件。AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。
Node节点
AQS维护了一个静态内部类Node,它包含了以下属性:
- static final Node SHARED:一种标识,表示被标识的结点是共享式。
- static final Node EXCLUSIVE:一种标识,表示被标识的结点是独占式。
- volatile int waitStatus:当前结点的等待状态,它有1,0,-1,-2,-3五个取值。
- static final int CANCELLED = 1; 被中断或者获取同步状态超时会被置为该状态,且在该状态下的线程不再被阻塞。
- 0为初始化默认值。
- static final int SIGNAL = -1; 线程如果释放了或者取消了同步状态,则会将对应的结点置为该状态,用于通知下一个节点,准备获取同步状态。
- static final int CONDITION = -2; 当前节点在Condition中的等待队列上,其他线程调用了singal方法后,该节点会从等待队列转移到AQS的同步队列上,等待获取同步锁。
- static final int PROPAGATE = -3; 与共享式获取同步状态有关,该状态标识的结点对应线程处于可运行的状态。
- volatile Node prev:前驱结点(给CLH同步队列使用)
- volatile Node next:后继结点(给CLH同步队列使用)
- volatile Thread thread:当前节点对应的线程
- Node nextWaiter:当前节点在Condition中等待队列上的下一个节点(给Condition等待队列使用)
上述属性中,注意其中a、b以及c中的状态信息都是static类型的,也就是说它们只是一些标识信息,是所有节点共用的。而其他属性则是每个节点自身的信息。
CLH同步队列
CLH(Craig, Landin, and Hagersten locks) 同步队列 是一个FIFO双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node。AQS依赖它来完成同步状态state的管理,CLH提供了一些重要的函数:
线程同步状态相关
- tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态
- tryRelease(int arg):独占式释放同步状态;
- tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;
- tryReleaseShared(int arg):共享式释放同步状态;
- acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;
- acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回;
- tryAcquireNanos(int arg,long nanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;
- acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
- acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断;
- tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制;
- release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;
- releaseShared(int arg):共享式释放同步状态;
CLH状态相关
- getState():返回同步状态的当前值;
- setState(int newState):设置当前同步状态;
- compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性;
- CLH队列入列就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。
同步状态的获取与释放
在AQS中维护着一个FIFO(先进先出)的同步队列CLH,当线程获取同步状态失败后,则会加入到这个CLH同步队列的对尾并一直保持着自旋。在CLH同步队列中的线程在自旋时会判断其前驱节点是否为首节点,如果为首节点则不断尝试获取同步状态,获取成功则退出CLH同步队列。当线程执行完逻辑后,会释放同步状态,释放后会唤醒其后继节点。
获取同步状态(入口:acquire相关方法)
释放同步状态(入口:release相关方法)
Condition等待队列
在经典的生产者-消费者模式中,可以使用Object.wait()和Object.notify()阻塞和唤醒线程,但是只能有一个等待队列。在AQS中也提供了类似的机制,但是可以更灵活的建立多个等待队列。Condition队列有以下特点:
- 提供了await()方法使一个线程陷入等待,并提供signal()方法来支持线程的唤醒。
- Condition和Wait/Notify通知机制类似,必须在Lock中使用,即在使用之前必须是已经获取到锁的状态(Wait/Notify必须在synchronized中使用)。
- 执行signal()方法后,等待队列的结点被转移至CLH同步队列中竞争锁。
Condition等待队列也使用了和CLH同步队列一样的结点,类似地使用firstWaiter与lastWaiter两个指针指向队列首尾,但是只使用了node的nextWaiter属性(而没有用next和prev)使其变成了一个单向链表(也是FIFO)。Condition等待队列提供了一些控制线程状态的重要函数:
- 线程等待阻塞
- await():让当前线程进入等待阻塞状态,使其转移到condition队列中,直到其他线程唤醒或者中断此线程。
- await(long time, TimeUnit unit):让当前线程进入等待阻塞状态,使其转移到condition队列中,直到其他线程唤醒、中断、超时。
- awaitNanos(long nanosTimeout):纳秒级别控制,让当前线程进入等待阻塞状态,使其转移到condition队列中,直到其他线程唤醒、中断、超时,并返回剩余时间。
- awaitUninterruptibly():让当前线程进入等待阻塞状态,且不会响应线程的中断。
- awaitUntil(Date deadline):类似于第二种情况。
- 线程唤醒
- signal():唤醒一个等待在Condition上的线程,使其转到同步队列中
- signalAll():唤醒所有等待在Condition上的线程,使它们转到同步队列中
LockSupport
可以发现,在同步队列和等待队列中对线程的很多操作都与LockSupport有关。LockSupport对外提供了一些静态方法用来操控当前线程:
- 线程阻塞相关
- park():阻塞当前线程,直到其他线程调用unpark(Thread)方法或者当前线程被中断
- parkNanos(long):阻塞当前线程,直到其他线程调用unpark(Thread)方法、当前线程被中断、超时(相对时间)
- parkUntil(long):阻塞当前线程,直到某个时间(绝对时间)
- 线程唤醒相关
- unpark(Thread):唤醒某个线程
而LockSupport的底层还是由Unsafe类来完成。