1.AQS简介
AQS是一个抽象类AbstractQueuedSynchronizer(抽象队列同步器)。它是基于等待队列
用来实现同步锁(ReentrantLock,Semaphore,CountdownLatch,CyclicBarrier,Exchanger等等)核心组件的基础框架,它本身没有实现任何的同步接口,只是定义了获取以及释放同步状态的方法
来提供自定义的同步组件。
2.实现原理
AQS中定义了一个使用volatile修饰state变量,用来标志同步状态
,并保证线程之间的可见性。还定义了一个同步队列
(使用Node的双向链表来实现)来保存竞争锁失败后的线程信息。
首先看下state变量:
private volatile int state;
当线程cas修改state的值成功后,设置加锁线程为当前线程,如果失败,则把失败的线程信息加入到同步队列。
2.2Node
Node是AQS中的一个内部类:AQS使用Node保存竞争锁失败的线程信息。
Node结构如下:
static final class Node {
volatile int waitStatus;//线程节点状态
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;//锁模式的标志
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
首先说明一下Node通过Node.mode节点定义了两种资源共享的方式,独占模式和共享方式。
/** 表示这个节点是在共享模式下 */
static final Node SHARED = new Node();
/** 表示该节点是独占模式 */
static final Node EXCLUSIVE = null;
//通过nextWaiter 判断锁模式
final boolean isShared() {
return nextWaiter == SHARED;
}
其中waitStatus代表每个线程的状态,它有如下值:
/** 线程已经被取消 */
static final int CANCELLED = 1;
/** 线程需要去被唤醒:获取同步状态失败,就挂起线程 */
static final int SIGNAL = -1;
/** 表示当前节点在condition队列*/
static final int CONDITION = -2;
/* 线程的共享锁应该被无条件向下传播*/
static final int PROPAGATE = -3;
//0:无状态,表示当前节点在队列中等待获取锁
值得说一下的是,如果刚开始看并发相关的源码,很容易看着看着,就把state与Node节点的waitStatus搞混了,所以首次看注意下他们的区别。
3.实现源码
3.1获取锁
//独占锁模式下获取锁
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//共享模式下获取锁
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//其实共享模式下更多的还是使用此方法获取锁
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
AQS在不同锁模式下使用不同的方法来获取锁,比如独占锁中ReentrantLock.lock();是使用的acquire()方法实现,共享锁中的CountDownLatch.countDown()方法就是使用的acquireSharedInterruptibly()方法实现的。
其中tryAcquire(arg)、tryAcquireShared(arg)分别是独占模式下和共享模式下获取锁的方法,都由子类实现具体的获取锁逻辑。
3.2释放锁
//独占模式下的释放锁过程
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//共享模式下的释放锁过程
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
AQS在不同锁模式下使用不同的方法来释放锁,比如独占锁中ReentrantLock.unlock();是使用的release()方法实现,共享锁中的CountDownLatch.countDown()方法就是使用的releaseShared()方法实现。
其中tryRelease(arg)、tryReleaseShared(arg)分别是独占模式下和共享模式下释放锁的方法,都由子类实现具体的释放锁的逻辑。
4.独占锁和共享锁的区别
其实这里说锁的概念,在独占模式中还能理解,但是在共享模式中调用countDown()方法再说获取锁就能难理解。所以我感觉这里说state是一个许可
更为容易理解。在独占模式下有线程获取到许可的过程就是state值从0=>1的过程,只有获取到许可的线程进行,重入的操作state值会从1=>2,在共享模式下当调用countDown()方法时,就是一个线程获取到一个许可,state的值-1,只有当许可消耗完,也就是state=0时才会恢复队列中挂起的线程。
5.总结
从上面AQS获取锁和释放锁的源码中可以看到,在获取锁和释放锁的过程中,具体对锁的获取和释放都是由其具体的子类来实现的
。这也是AQS的精髓之处。
下面我会写两篇博客来分析ReentrantLock和CountDownLath的源码,具体分析AQS在独占和共享模式下的具体实现。