简介
JDK1.5之后,Java提供了Lock以及众多用于的并发开发的类(例如:ReentrantLock、CountDownLatch、CyclicBarrier等),而这些类的实现大部分都是基于AbstractQueuedSynchronizer(队列同步器,简称AQS)来实现。
AbstractQueuedSynchronizer内部有一个int变量表示的同步状态(同步状态通过getState setState compareAndSetState来维护,同时这三个方法能够保证线程安全),以及一个FIFO双向队列,一般称这个队列为同步队列。当线程获取资源(竞争同步状态)失败就会进去同步队列排队。
AQS是个抽象类(但是这个抽象类中并没有抽象方法),同步组件一般通过维护AQS的继承子类来实现。AQS既支持独占地获取同步状态,又支持共享地获取同步状态,从而实现不同类型的组件。
AQS是基于模板方法,同步组件需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。以下是AQS中可重写的方法。
- protected boolean tryAcquire(int arg) : 独占式获取同步状态,试着获取,成功返回true,反之为false;
- protected boolean tryRelease(int arg) :独占式释放同步状态,等待中的其他线程此时将有机会获取到同步状态;
- protected int tryAcquireShared(int arg) :共享式获取同步状态,返回值大于等于0,代表获取成功;反之获取失败;
- protected boolean tryReleaseShared(int arg) :共享式释放同步状态,成功为true,失败为false;
- protected boolean isHeldExclusively() : 是否在独占模式下被线程占用。
这几个类在AQS中的默认类都是简单的抛出UnsupportedOperationException异常
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
AQS实现分析
AQS的内部通过Node内部类一个个连接起来实现FIFO同步队列。Node类的结构如下所示:waitStatus表示Node的状态,它的取值定义在Node中。另外AQS是共享还是独占地获取同步状态,在Node内中也定义了对应的标记属性。
static final class Node {
/** 共享模式 */
static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node();
/** 独占模式 */
static final AbstractQueuedSynchronizer.Node EXCLUSIVE = null;
/** 表示线程已被取消(等待超时或者被中断) */
static final int CANCELLED = 1;
/** 表示后继节点中的线程需要被唤醒(unpaking) */
static final int SIGNAL = -1;
/** 表示结点线程等待在condition上(等待队列),当被signal后,会从等待队列转移到同步到队列中 */
static final int CONDITION = -2;
/** 表示下一次共享模式下同步状态会被无条件地传播下去 */
static final int PROPAGATE = -3;
/** 除了上面定义的4中状态之外,waitStatus在初始化还能取值为0,即表示初始状态 */
}
独占模式获取同步状态
独占模式下获取同步状态的方法有:tryAcquireNanos(int arg, long nanosTimeout)、acquireInterruptibly(int arg)、acquire(int arg)。
tryAcquireNanos既能支持线程响应中断(即在同步队列中,如果线程被中断,则方法会抛出InterruptedException异常),又能支持超时;acquireInterruptibly方法只支持响应中断;acquire中断和超时都不支持。下面看看acquire(int arg)内部实现
public final void acquire(int arg) {
/**
* tryAcquire:获取同步状态,同步组件中的子类中自己重写实现
* addWaiter:将当前线程放入Node节点中,再将Node节点放入同步队列中
* acquireQueued:在同步队列中自旋获取同步状态,这个方法会使节点中的线程阻塞等待
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire方法中做了如下几件事
- 尝试获取同步状态,如果成功直接返回,失败则继续;
- 构造Node节点,将当前线程放入其中;
- 将构造好的Node节点放入由Node组成的同步队列;
- Node中的线程在同步队列中自旋等待。
tryAcquire(int arg)方法的作用就是让同步组件自己实现的,所以实现得看具体组件。下面看看addWaiter方法是如何实现的
private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node mode) {
//使用当前线程构造node节点
AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(
Thread.currentThread(), mode);
AbstractQueuedSynchronizer.Node pred = tail;
//队列不为空
if (pred != null) {
node.prev = pred;
/* 通过cas将当前节点放入同步队列的尾部,因为多线程环境下获取锁成功的线程只有一个,
* 所以会有多个线程需要放入同步队列尾部,因此此处需要通过cas保证线程安全。
* 同时compareAndSetTail可能失败,所以下面得调用enq(node)方法,执行重复的代码
*/
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private AbstractQueuedSynchronizer.Node enq(final AbstractQueuedSynchronizer.Node node) {
for (;;) { //通过死循环和compareAndSetTail方法保证当前线程一定能放入队尾
AbstractQueuedSynchronizer.Node t = tail;
if (t == null) {
if (compareAndSetHead(new AbstractQueuedSynchronizer.Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq内部是个死循环,通过CAS设置尾结点,不成功就一直重试,很经典的CAS自旋的用法。
acquireQueued方法会使得同步队列中的每个节点自旋获取同步状态,具体实现如下
final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 自旋等待
for (;;) {
//找到当前结点的前驱结点
final AbstractQueuedSynchronizer.Node p = node.predecessor();
// 只有前驱节点是head,才尝试获取同步状态
if (p == head && tryAcquire(arg)) {
//获取同步状态成功,将当前结点设置为头结点。
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/**
* shouldParkAfterFailedAcquire: 判断是否应该阻塞当前线程
* parkAndCheckInterrupt:真正让线程阻塞等待
* 阻塞之前先判断是否能阻塞
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued内部自旋,判断当前节点是否为老二节点(head是老大节点),当前节点是老二节点就去尝试获取同步状态,如果获取成功则返回,否则的话阻塞当前线程。
线程阻塞通过LockSupport.park(this);来实现。但是在调用阻塞之前会先通过shouldParkAfterFailedAcquire方法通过waitStatus的值来判断是否需要调用阻塞。
private static boolean shouldParkAfterFailedAcquire(AbstractQueuedSynchronizer.Node pred, AbstractQueuedSynchronizer.Node node) {
//获取前驱节点的waitStatus值
int ws = pred.waitStatus;
//若前驱结点的状态是SIGNAL,意味着当前结点可以被安全地唤醒(park)
if (ws == AbstractQueuedSynchronizer.Node.SIGNAL)
return true;
if (ws > 0) {
/* ws>0,只有CANCEL状态ws才大于0。若前驱结点处于CANCEL状态,也就是此结点线程
* 已经无效,从后往前遍历,找到一个非CANCEL状态的结点,将自己设置为它的后继结点
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 若前驱结点为其他状态,将其设置为SIGNAL状态
compareAndSetWaitStatus(pred, ws, AbstractQueuedSynchronizer.Node.SIGNAL);
}
return false;
}
可以看到只有前驱节点的waitStatus是SIGNAL,当前才需要调用阻塞。
整个独占模式下获取同步状态的流程图如下
独占模式释放同步状态
线程执行完自己的逻辑之后,会释放同步状态。独占模式下释放同步状态的方法如下
public final boolean release(int arg) {
//尝试释放同步状态,成功的话唤醒后继节点,该方法使用者要重写
if (tryRelease(arg)) {
AbstractQueuedSynchronizer.Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
unparkSuccessor的实现如下
private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {
// 获取waitStatus,并将其设置为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 获取头节点的后继节点
AbstractQueuedSynchronizer.Node s = node.next;
if (s == null || s.waitStatus > 0) { // waitStatus>0表示取消状态
s = null;
//从队尾往前遍历找到一个处于正常阻塞状态的结点进行唤醒
for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 相当于通知(notify),将后继节点唤醒
LockSupport.unpark(s.thread);
}
释放同步状态时,会将头节点的后继节点唤醒。
共享模式获取同步状态
共享式同步组件来讲,同一时刻可以有多个线程同时获取到同步状态。
public final void acquireShared(int arg) {
// tryAcquireShared尝试获取同步状态,使用者需要重写实现
if (tryAcquireShared(arg) < 0)
//代码执行到这里,表示同步状态获取失败,需要排队
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
// 构建共享节点,放入同步队列中
final AbstractQueuedSynchronizer.Node node = addWaiter(AbstractQueuedSynchronizer.Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 前驱节点
final AbstractQueuedSynchronizer.Node p = node.predecessor();
//只有前驱是头结点,才有机会尝试获取同步状态
if (p == head) {
//尝试获取同步状态
int r = tryAcquireShared(arg);
if (r >= 0) {
////获取成功就将当前结点设置为头结点,若还有可用资源,传播下去,也就是继续唤醒后继结点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以看到,共享模式下的同步状态获取基本和独占模式类似。
共享模式释放同步状态
共享模式下,由于有多个线程持有同步状态,所以释放的时候需要保证线程安全。
private void doReleaseShared() {
//死循环,持有同步状态的线程可能有多个,采用循环CAS保证线程安全
for (;;) {
AbstractQueuedSynchronizer.Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
// 当头节点的ws为SIGNAL才唤醒后继节点
if (ws == AbstractQueuedSynchronizer.Node.SIGNAL) {
// 将头节点的waitStatus置为0
if (!compareAndSetWaitStatus(h, AbstractQueuedSynchronizer.Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒后继节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, AbstractQueuedSynchronizer.Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
通过AQS实现一个自定义锁
在我们的自定义锁中,维护一个AQS子类的内部类,该AQS子类重写tryAcquire(int acquire)和tryRelease(int releases)方法。然后加锁调用AQS的模板方法acquire(int arg),释放锁调用AQS的模板方法release(int arg)即可。具体实现如下
public class Mutex implements Lock,Serializable {
// 定义AQS的子类,主要依靠这个子类来实现定义锁
private static class Sync extends AbstractQueuedSynchronizer {
// 锁是否被占用
protected boolean isHeldExclusively() {
return getState() == 1;
}
// state=0时,获取锁,state+1
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 释放锁,让state置为0
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// Provide a Condition
Condition newCondition() { return new ConditionObject(); }
// Deserialize properly
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();
public void lock() { sync.acquire(1); }
public boolean tryLock() { return sync.tryAcquire(1); }
public void unlock() { sync.release(1); }
public Condition newCondition() { return sync.newCondition(); }
public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
参考
《Java并发编程的艺术》