Java并发(一)——AQS原理分析
AQS 简单介绍
AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面。
AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask(jdk1.7) 等等皆是基于 AQS 的。当然,我们自己也能利用 AQS 非常轻松容易地构造出符合我们自己需求的同步器。
AQS原理
AQS原理概览
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。
AQS(AbstractQueuedSynchronizer)原理图:
AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。
1 | /** |
状态信息通过 protected 类型的getState,setState,compareAndSetState进行操作
1 | /** |
AQS对资源的共享方式
独占方式 Exclusive
在独占方式下,获取与释放资源的流程如下:
- 当一个线程调用acquire(int arg)方法获取独占资源时,首先会使用tryAcruire方法尝试获取资源,具体是设置状态变量state得值,成功则直接放回,失败则将当前线程封装成Node.EXCLUSIVE的节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己。
1 | public final void acquire(int arg) { |
- 当一个线程调用relase(int arg) 方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryAcquire尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。
1 | public final boolean release(int arg) { |
需要注意的是,AQS类并没有提供可用的tryAcquire和tryRelease方法,需要由具体子类来实现。子类在实现tryAcquire和tryRelease时要根据具体场景使用CAS算法尝试修改state的值,成功返回true,否则返回false。子类还需要定义,在调用acquire和release方法时state状态值的增减代表什么含义。
比如继承自AQS实现的独占锁ReentrantLock,定义当state为0时表示锁空闲,为1时表示锁已经被占用。在重写tryAcquire时,在内部需要使用CAS算法查看当前state是否为0,如果为0则设置为1,并设置当前锁的持有者为当前线程,而后返回true,如果CAS失败则返回false。
比如继承自AQS实现的独占锁在实现tryRelease时,在内部需要使用CAS算法把当前state的值从1修改为0,并设置当前锁的持有者为null,然后返回true,如果CAS失败则返回false。
共享方式
在共享方式下,获取与释放资源的流程如下:
- 当线程调用acquire(int arg)获取共享资源时,会首先使用tryAcquireShared尝试获取资源,具体是设置状态变量state的值,成功则直接返回,失败则将当前线程封装为类型为Node.SHARED的Node节点后插入AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己。
1 | public final void acquireShared(int arg) { |
- 当一个线程调用releaseShared(int arg)时会首先尝试使用tryReleaseShared操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark(thread)激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下执行,否则还是会被放入AQS队列被挂起。
1 | public final boolean releaseShared(int arg) { |
同样需要注意的是,AQS类并没有提供可用的tryAcquireShared和tryReleaseShared方法,正如AQS是锁阻塞和同步器的基础框架一样,tryAcquireShared和tryReleaseShared需要由具体的子类来实现。子类在实现tryAcquireShared和tryReleaseShared时要根据具体的场景使用CAS算法舱室修改state状态值,成功返回true,否则返回false。
比如继承自AQS实现的读写锁ReentrantReadWriteLock里面的读锁重写tryAcquireShared时,首先查看写锁是否被其他线程持有,如果是则返回false,否则使用CAS递增state的高16位(在ReentrantReadWriteLock中,state的高16位为后去读锁的次数)。在重写tryReleaseShared时,在内部需要使用CAS算法把当前state的高16位减1,然后返回true,如果CAS失败返回false。
下一章分析线程同步器CountDownLatch。