写在前面:
在这一篇博客中,将会介绍Java中的锁等相关内容,篇幅较长,会循序渐进的总结从队列同步器再到Lock接口的相关实现类。
队列同步器:
队列同步器 (AbstractQueuedSynchronizer) ,是用来构建锁和其他同步组件的基础,从Java API中,我们可以看出,在AbstractQueuedSynchronizer类中,定义了一个int 类型的变量-来记录同步的状态,(如果state=1则是获取了同步状态,=0则表示没有获取到),定义一个内部类Node--来实现一个双向队列(FIFO)。
private volatile int state;
static final class Node {}
队列同步器,主要是通过子类来继承队列同步器的一些方法。会重写队列同步器中独占/共享,获取释放同步状态的方法。
protected boolean tryAcquire(int arg) {
//独占式获取同步状态
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
//独占式释放同步状态
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
//共享式获取同步状态
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
//共享式释放同步状态
throw new UnsupportedOperationException();
}
protected boolean isHeldExclusively() {
//当前同步器是否在独占模式下被线程占用
throw new UnsupportedOperationException();
}
AbstractQueuedSynchronizer的设计师基于模板方法设计模式,使用者需要基础并重写同步器的指定方法。此外,在类中,提供了几个返回同步状态的方法,来方便子类修改线程的同步状态。
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
队列同步器的实现:
1. 同步队列
同步器依赖内部的同步队列来完成同步状态的管理。当前线程获取同步状态时,同步器会把当前线程及等待状态等信息构造成一个节点(Node),并加入同步队列。同时会阻塞当前线程,当同步状态释放时,会唤醒同步队列中首节点,使其能够再次获得同步状态。(同步状态即获取到锁)。
同步队列器中提供的是双向队列,且在同步队列器中提供了首节点(head)和尾节点(tail),没有获取同步状态的线程,将会被加入到队列的尾部。设置尾部节点的方法时上文中提到的:
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
同步器提供的基于CAS(compareAndSwap)的设置尾节点的方法。方法的参数需要传递两个参数,一个为当前线之前的尾部节点和当前节点。
此外,因为同步队列遵循先进先出原则,头节点是获取同步状态成功的节点。当首节点在释放同步状态时,将会唤醒后继节点,而后继节点在获取同步状态成功后,将自己设置为首节点。
2. 独占式同步状态的获取与释放
通过acquire方法可以获取到独占式同步状态,方法实现代码如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
上述代码中:
(1) 首先调用tryAcquire方法保证线程安全的情况下去获取同步状态
(2) 如果获取失败,则构造同步节点(独占式NODE.EXCLUSIVE)
(3) 然后通过addWaiter方法加入到同步队列的尾部
(4) 最后调用acquireQueued方法以死循环的方式获取同步状态
下面是实现过程的源码(JDK8):
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
addWaiter中通过compareAndSetTail来确保节点能够被线程安全添加。如果使用Linkedlist来维持一个节点之间关系,由于linkedlist很难保证Node的正确添加和顺序不乱。
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq方法中,用了一个for的死循环来保证节点的正确添加,enq方法实际上是将并发添加节点的程序,通过compareAndSetTail中的CAS,将节点的添加串行化解决。
注:CAS,即compareAndSwap,是由sun包下的Unsafe类实现。而Unsafe类中的方法都是native方法,由JVM本地实现。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquiredQueued方法会不断地尝试获取同步状态,但是只要前驱节点才能尝试获取同步状态,因为:
(1) 因为在头部节点在成功获取到同步状态的结点,在他释放同步状态之后,将会唤醒他的后继节点。
(2) 维护FIFO的原则。
自旋过程:是指前驱节点为头节点且能够获取同步状态的判断条件和线程进入等待状态。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
在当前线程获取同步状态(即获取到锁)之后就需要释放同步状态,使得后继节点能够持续获取同步状态。
总结:
在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会加入到队列中并在队列中进行自旋。移除队列的条件是前驱节点是头节点且成功获取了同步状态。调用tryRelease方法释放同步状态,同时唤醒头节点的后继节点。
3. 共享式同步状态的获取与释放
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
通过调用acquiredShared方法就可以共享地,即多线程同时获取同步状态,在acquiredShared方法中又调用了doAcquiredShared方法。
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final 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);
}
}
与独占式获取同步状态一样,获取到之后,会释放掉同步状态,与与独占式不同的是,共享式必须保证线程安全,因为在释放同步状态的同事要操作多个线程。
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
4. 独享式超时获取同步状态
通过调用doAcquireNanos方法可以超时获取同步状态,即在指定时间内获取同步状态。获取到返回true,未获取到返回false。
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}