日常开发中,大多数程序员并不会直接接触AbstractQueuedSynchronizer(AQS)类,但其在并发工具中缺无处不在,并作为内部的标准同步器,如ReentrantLock,Semaphore,Java线程池中的Worker等。本文将介绍AQS相关的实现细节。
-
什么是AbstractQueuedSynchronizer(AQS)
AQS负责管理同步器类中的状态,它管理了一个整数状态信息,可以通过getState,setState及compareAndSetState等方法进行操作。这个整数状态的意义由子类来赋予,如ReentrantLock中该状态值表示所有者线程已经重复获取该锁的次数,Semaphore中该状态值表示剩余的许可数量。可以看下使用的AbstractQueuedSynchronizer的并发工具类:
-
AbstractQueuedSynchronizer(AQS)实现
AQS定义比较简单,继承自AbstractOwnableSynchronizer接口:
-
AbstractOwnableSynchronizer
当一个同步器可以由单个线程独占时,AbstractOwnableSynchronizer定义了基础的创建锁和相关同步器的方法,但其本身并不管理维护这些信息,而是交由子类去实现:
-
AbstractQueuedSynchronizer
AbstractQueuedSynchronizer内部使用CLH锁(CLH锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋)的变种来实现对线程的阻塞。CLH锁的链表中的节点被抽象为Node:
其中AbstractQueuedSynchronizer维护的链表结构大致如下:
-
ReentrantLock
可以先从ReentrantLock的实现来探究AbstractQueuedSynchronizer的作用。ReentrantLock内部封装了一个Sync类,来实现基本的lock和unlock操作:
-
当ReentrantLock执行lock()时,主要是通过AbstractQueuedSynchronizer的acquire()方法实现:
下图揭示了从同步器获取锁时,内部的等待队列的状态变化图:
初始状态
当只有一个线程t1进行lock()操作时,由于tryAcquire()将返回true,不用进行等待,等待队列状态不变。
若在线程t1还未unlock(),线程t2就进行了lock()操作,此时等待队列将被初始化,并将线程t2插入等待队列:
此时,若线程t3也进行lock()操作:
以上则是ReentrantLock的加锁(lock)机制,下面则是ReentrantLock的解锁(unlock)机制:
一旦 LockSupport.unpark(s.thread);执行完,对应的等待节点将被唤醒:
以上,则是AbstractQueuedSynchronizer同步器的基本实现机制,其作为很多并发工具的基础,规范了如何阻塞和唤醒线程,相比普通的锁机制(如synchronized),其通过自旋等待和精确唤醒,可以提高一些并发时的性能。
-
参考文献