共计 4341 个字符,预计需要花费 11 分钟才能阅读完成。
一、AQS 根本介绍
同步器 AbstractQueuedSynchronizer(简称 AQS)是用来 构建其余同步组件的根底 ,它应用了一个 int 变量来示意 同步状态 state,通过 内置的 FIFO 队列 来实现 线程的排队工作。
二、如何应用 AQS 来构建同步组件?
同步器的设计是基于 模板模式 的,使用者继承同步器并重写指定的办法。同步器可重写的办法如下:
办法名称 | 形容 |
---|---|
boolean tryAcquire(int arg) | 尝试独占式获取同步状态,实现该办法须要查问以后状态并判断同步状态是否合乎预期,而后再进行 CAS 设置同步状态 |
boolean tryRelease(int arg) | 尝试独占式开释同步状态,期待获取同步状态的线程将有机会获取同步状态 |
int tryAcquireShared(int arg) | 尝试共享式获取同步状态,返回大于等于 0 的值,示意获取胜利,反之获取失败。 |
boolean tryReleaseShared(int arg) | 尝试共享式开释同步状态 |
boolean isHeldExclusively() | 以后同步器是否在独占模式下被线程占用,个别该办法示意是否被以后线程独占 |
实现自定义同步组件时,将会调用同步器提供的模板办法,如下:
办法名称 | 形容 |
---|---|
void acquire(int arg) | 独占式获取同步状态 ,如果以后线程获取同步状态胜利,则由该办法返回,否则将会 进入同步队列期待,该办法会调用重写的 tryAcquire 办法 |
void acquireInterruptibly(int arg) | 与 acquire 雷同,然而该办法 响应中断。如果以后线程未获取到同步状态而进入同步队列,如果以后线程被中断,则该办法会抛出 InterruptedException 异样 |
boolean tryAcquireNanos(int arg, long nanos) | 在 acquireInterruptibly 根底上减少了 超时限度,如果以后线程在超时工夫内没有获取到同步状态,则返回 false,反之返回 true |
void acquireShared(int arg) | 共享式地获取同步状态,如果以后线程未获取到同步状态,将会进入同步队列期待,与独占式次要区别在于同一时刻能够有多个线程获取到同步状态 |
void acquireSharedInterruptibly(int arg) | 与 acquireShared 雷同,该办法响应中断 |
boolean tryAcquiredSharedNanos(int arg, long nanos) | 在 acquireSharedInterruptibly 根底上减少了超时限度 |
boolean release(int arg) | 独占式的开释同步状态,该办法在开释同步状态之后,将同步队列的第一个节点的线程唤醒 |
boolean releaseShared(int arg) | 共享式的开释同步状态 |
Collection getQueuedThreads() | 获取期待在同步队列上的线程汇合 |
三、AQS 的实现剖析
从实现角度来剖析同步器是如何实现线程同步?
- 同步队列
- 独占式同步状态获取与开释
- 共享式同步状态获取与开释
- 超时获取同步状态
3.1 同步队列
AQS 外部基于同步队列(一个双向队列)来实现同步状态的治理。以后线程获取同步状态失败时,会将以后线程以及期待状态等信息结构成为一个 Node 节点并将其退出同步队列,同时阻塞以后线程,当同步状态开释,会把首节点中的线程唤醒,使其再次获取同步状态。
Node 类构造如下:
属性类型与名称 | 形容 |
---|---|
int waitStatus | 期待状态。1、CANCELLED,值为 1,因为在同步队列中期待的线程期待超时或者被中断,须要从同步队列中勾销期待,节点进入该状态将不会变动。2、SIGNAL,值为 -1,后继节点的线程处于期待状态,而以后节点的线程如果开释了同步状态或被勾销,将会告诉后继节点,使后继节点的线程得以运行。3、CONDITION,值为 -2,节点在期待队列中,节点线程期待在 Condition 上,当其余线程对 Condition 调用了 signal()办法后,该节点将会从期待队列中转移到同步队列中,退出到对同步状态的获取中。4、PROPAGATE,值为 -3,示意下一次共享式同步状态获取将会无条件的被流传上来。5、INITIAL,值为 0,初始状态 |
Node prev | 前驱节点,当节点退出同步队列时被设置 |
Node next | 后继节点 |
Node nextWaiter | 期待队列(后续章节会介绍)中的后继节点。如果以后节点是共享的,那么这个字段是 SHARED 常量。也就是说节点类型(独占或共享)和期待队列中的后继节点共用同一个字段 |
Thread thread | 对应的线程 |
Node 节点是形成同步队列的根底,没有胜利获取同步状态的线程将会成为节点退出该队列的尾部,头节点是领有同步状态的节点。同步队列的根本构造如下:
3.2 独占式获取、开释同步状态
获取同步状态:通过调用同步器的 acquire(int arg)办法获取同步状态,该办法无奈响应中断。下述代码实现了同步状态获取、节点结构、退出同步队列等相干工作。
public final void acquire(int arg) {if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();}
流程如下:
- 调用自定义同步器的 tryAcquire(int arg)办法,该办法保障线程平安的获取同步状态,如果获取胜利,则设置独占线程为以后线程,如果获取失败,结构节点并通过 addWaiter(Node node)办法将该节点退出到同步队列的尾部。
- 接着调用 acquireQueued(Node node, int arg)办法,使得节点再次尝试获取同步状态
- 如果获取不到会调用 LockSupport.park()办法阻塞以后线程,而 被阻塞线程的唤醒 次要依附 前驱节点的出队或阻塞线程被中断 来实现。
开释同步状态:以后线程获取同步状态并执行了相应逻辑后,就须要开释同步状态,使得后续节点可能持续获取同步状态。通过调用同步器的 release(int arg) 办法就能够开释同步状态,该办法在开释同步状态后,会唤醒其后继节点。
public final boolean release(int arg) {if(tryRelease(arg)) {
Node h = head;
if(h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
总结:
- 获取同步状态时,同步器保护一个同步队列,获取状态失败的线程会被退出到队列尾部。这里会先判断一下前驱节点是否是头节点,如果是,则尝试获取同步状态,如果获取胜利,间接将该节点设置为头节点,如果获取失败,将以后线程挂起。
- 开释同步状态时,同步器调用 release(int arg)办法开释同步状态,唤醒头节点的后继节点。
3.3 共享式获取、开释同步状态
通过调用同步器的 acquireShared(int arg) 办法能够共享式地获取同步状态。
public final void acquireShared(int arg) {if(tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
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;
if(interrupted)
selfInterrupt();
failed = false;
return;
}
}
if(shouldParkAfterFailedAcquire(p, node)
&& parkAndCheckInterrupt())
interrupted = true;
}
} finally {if(failed) {cancelAcquire(node);
}
}
}
在 acquireShared(int arg)办法中,同步器调用 tryAcquireShared(int arg)办法尝试获取同步状态,办法返回值为 int 值,如果大于等于 0,示意可能获取到同步状态,反之无奈获取同步状态 。在 doAcquireShared(int arg) 办法的自旋过程中,如果以后节点的前驱为头节点,尝试获取同步状态,如果返回值大于等于 0,示意该次获取同步状态胜利并从自旋过程中退出。
与独占式一样,共享式也须要开释同步状态,通过调用 releaseShared(int arg)办法能够开释同步状态。它与独占式地次要区别在于 tryReealseShared(int arg)办法必须确保同步状态平安开释,个别是通过 循环 +CAS来保障的。
public final boolean releaseShared(int arg) {if(tryReleaseShared(arg)) {doReleaseShared();
return true;
}
return false;
}
3.4 超时获取同步状态
通过调用同步器的 doAcquireNanos(int arg, long nanosTimeout) 办法能够超时获取同步状态,在指定的时间段内获取同步状态,如果获取到则返回 true,否则返回 false。
超时获取同步状态过程 是响应中断获取同步状态过程 的升级版,doAcquireNanos 办法在 反对响应中断的根底上,减少了超时获取的个性,该办法提供了synchronized 关键字不具备的个性。
独占式超时获取同步状态和独占式获取同步状态在流程上十分相似,次要区别在于未获取到同步状态时的解决逻辑。acquire(int arg)在未获取到同步状态时,将会使以后线程始终处于期待状态,而 doAcquireNanos()办法会使以后线程期待 nanosTimeout 纳秒,如果以后线程在 nanosTimeout 纳秒内没有获取到同步状态,将会间接返回 false。
四、总结
自定义同步组件可通过继承 AQS 并实现它的形象办法来治理 同步状态 。同步器既能够反对 独占式 地获取同步状态,也能够反对 共享式 地获取同步状态,来实现不同类型的同步组件(如 ReentrantLock 可重入锁、ReentrantReadWriteLock 读写锁和 CountDownLatch 锁存器)