1、AQS(AbstractQueuedSynchronizer)
-
一个 volatile 的整数成员代表状态,同时提供了 setState 和 getState 方法;
private volatile int state;
protected final int getState() { return state; }
protected final void setState(int newState) { state = newState;
-
一个先入先出(FIFO)的等待线程队列(内部定义Node类,为双向链表,用来代表队列的元素),以实现多线程间竞争和等待,这是 AQS 机制的核心之一;
-
各种【基于 CAS】 的操作方法,以及各种期望具体同步结构去实现的 【acquire/release】方法;
-
利用 AQS 实现一个同步结构,至少要实现两个基本类型的方法,分别是 acquire 操作,获取资源的独占权;还有就是 release 操作,释放对某个资源的独占。
1-1、条件队列
条件队列是一种【状态依赖管理】的实现方式。
【可阻塞】的【状态依赖】操作的结构
acquire lock on object state
while(precondtion does not hold) {
release lock
wait until precondition might hold
optionally fail if interruputted or timeout expires
reacquire lock
}
perform action
release lock
条件队列:是的一组【线程】能够通过某种方式来【等待特定的条件变真】,
所以条件队列中的元素是【正在等待相关条件成立】的【线程】。
Object 中的 wait、notify和notifyAll构成了内部调节队列API;
只有能对状态进行检查时【类似 while (condition not true)】,才能在某个条件上等待,并且只有能修改状态时【类似 while (condition true)】,才能从条件等待中释放另一个线程。
1-2、Object的 wait、notify 和 notifyAll 实现条件队列
Object.wait 会【自动释放锁】,并请求操作系统挂起当前线程,从而使其他线程能够获得这个锁并修改对象的状态;当被挂起的线程醒来时,它将在返回之前重新获取锁;
1-3、使用等待条件时需要满足的要求
- 先持有与条件队列相关的锁,并且这个锁来维护构成条件谓词的状态变量;
- 要有一个条件谓词,满足条件继续执行,否则等待;
- 调用wait前必须执行条件谓词,从wait状态返回到runnable状态需要再次执行条件谓词;
- 在一个循环中调用wait;
- 检查条件谓词之后,以及开始执行相应操作前,都不要释放锁;
示例代码:
// 1、先持有锁
public synchronized void put(V v) throws InterruptedException {
// 2、条件谓词判断是否即系执行
while (isFull()) {
// 3、在循环中执行wait
wait();
}
// 条件谓词: not-full,队列不满,才能放
doPut(v);
// 不能忘记通知
notifyAll();
}
1-4、源码分析AQS
-
AQS内部有一个 volatile 的整数成员代表同步状态,同时提供了 setState 和 getState 方法,比如可重入锁的计数值;
private volatile int state;
-
一个【先入先出(FIFO)】的等待线程队列,以实现多线程间竞争和等待,这是 AQS 机制的核心之一。
-
各种基于【CAS】 的基础操作方法,以及各种期望具体同步结构去实现的 acquire/release 方法。
以ReentrantLock为例分析AQS源码:
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
}
static final class NonfairSync extends Sync {
final void lock() {
acquire(1);
}
}
static final class FairSync extends Sync{
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
采用【acquire】方法进行获取锁操作,类似Lock中的 lock 方法,依赖操作有:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
-
tryAcquire:protected,如果子类是一个独占锁,需要子类实现此方法;在 ReentrantLock 中,tryAcquire 逻辑实现在 NonfairSync 和 FairSync 中,分别提供了进一步的非公平或公平性方法,而 AQS 内部 tryAcquire 是个未实现的方法(直接抛异常),这是留个实现者自己定义的操作。以非公平的 tryAcquire 为例,其内部实现了如何配合状态与 CAS 获取锁,注意,对比公平版本的 tryAcquire,它在锁无人占有时,并不检查是否有其他等待者,这里体现了非公平的语义。
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState();// 获取当前AQS内部状态量 if (c == 0) { // 0表示无人占有,则直接用CAS修改状态位, if (compareAndSetState(0, acquires)) {// 不检查排队情况,直接争抢 setExclusiveOwnerThread(current); //并设置当前线程独占锁 return true; } } else if (current == getExclusiveOwnerThread()) { //即使状态不是0,也可能当前线程是锁持有者,因为这是再入锁 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
-
acquireQueued,如果前面的 tryAcquire 失败,代表着锁争抢失败,进入排队竞争阶段。这里就是我们所说的,利用 FIFO 队列,实现线程间对锁的竞争的部分,算是是 AQS 的核心逻辑。
-
当前线程会被包装成为一个排他模式的节点(EXCLUSIVE),通过 addWaiter 方法添加到队列中。acquireQueued 的逻辑,简要来说,就是如果当前节点的前面是头节点,则试图获取锁,一切顺利则成为新的头节点;否则,有必要则等待。
-
到这里线程试图获取锁的过程基本展现出来了,tryAcquire 是按照特定场景需要开发者去实现的部分,而线程间竞争则是 AQS 通过 Waiter 队列与 acquireQueued 提供的,在 release 方法中,同样会对队列进行对应操作。
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { for (;;) {// 循环 boolean interrupted = false; final Node p = node.predecessor();// 获取前一个节点 if (p == head && tryAcquire(arg)) { // 如果前一个节点是头结点,表示当前节点合适去tryAcquire setHead(node); // acquire成功,则设置新的头节点 p.next = null; // 将前面节点对当前节点的引用清空 return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 检查是否失败并且需要park成功后,设置中断 interrupted = true; } } finally { if (failed) { cancelAcquire(node); // 如果失败,取消 } } }
1-4-1、独占模式获取
1-4-2、共享模式获取
采用【acquireShared】方法,进行获取操作,类似Lock中的 lock 方法,依赖操作有:
-
tryAcquireShared:protected,如果子类需要实现一个【共享】锁,要覆盖并实现此方法;
-
doAcquireShared;
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }
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); }
2、CountDownLoatch(闭锁)
闭锁是一种同步工具类,可以延迟线程的进度,直到其到达终止状态。
闭锁的作用相当于一扇门,可以理解为倒计时锁,或者门上有多个锁:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过。
当到达结束状态时,这扇门会打开并允许所有的线程通过。
当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。
使用场景:
- 确保某个计算在其需要的所有资源都被初始化后才继续执行;
- 确保某个服务在其依赖的所有其他服务器都已经启动后才启动;每个服务都有一个相关的二元闭锁。当启动服务S时,首先在S依赖的其他服务的闭锁上等待,在所有依赖的服务都启动后会释放闭锁S,这样其他依赖S的服务才能继续执行;
- 等待直到某个操作的所有参与者(如多玩家游戏的所有玩家,或者赛车和跑步选手)都就绪在继续执行,这种情况中,所有玩家都准备就绪时,闭锁将到达结束状态。
关于CountDownLatch:
- CountDownLatch的闭锁状态包含一个计数器,该计数器被初始化一个正数,表示需要等待事件的数量。
- countDown方法递减计数器,表示有一个事件已经发生了;
- wait方法等待计数器到零,表示所有需要等待的事件都已经发生;
- 如果计数器的值非零,那么await会一直阻塞直到计数器为零,或者等待中的线程中断,或者等待超时;
CountDownLatch唯一构造器带有一个int类型的参数,这个int参数是指【允许所有在等待的线程被处理之前,必须在锁存期上调用countDown方法的次数】。
允许一个或多个线程 等待 某些操作 完成。
2-1、内部同步队列
内部的同步状态使用【共享模式】,【获取】和【释放】操作需要Override;
private static final class Sync extends
AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
@Override
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
@Override
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
2-2、构造函数
初始化时构造函数参数为int,表示当前的计数值;用来作为【同步状态】;
private final Sync sync;
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
3、栅栏(CyclicBarrier)
一种辅助的同步结构,允许多个线程等待达到某个屏障;
所有线程必须同时到达栅栏位置,才能继续执行;
栅栏用于等待线程;
栅栏可以使一定数量的参与方反复地在栅栏位置汇集;
当线程到达栅栏位置时,将调用await方法,这个方法将阻塞所有线程都到达栅栏的位置;
如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有线程都被释放,栅栏将被重置以便下次继续使用;
比如,所有人集合的场景;
package java.util.concurrent;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class CyclicBarrier {
private static class Generation {
boolean broken = false;
}
/** The lock for guarding barrier entry */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
/** The number of parties */
private final int parties;
/* The command to run when tripped */
private final Runnable barrierCommand;
/** The current generation */
private Generation generation = new Generation();
/**
* Number of parties still waiting. Counts down from parties to 0
* on each generation. It is reset to parties on each new
* generation or when broken.
*/
private int count;
/**
* Updates state on barrier trip and wakes up everyone.
* Called only while holding lock.
*/
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
/**
* Sets current barrier generation as broken and wakes up everyone.
* Called only while holding lock.
*/
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
/**
* Main barrier code, covering the various policies.
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
public int getParties() {
return parties;
}
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
4、信号量(Semaphore)
Java对于信号量的实现。
计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。信号量还可以用来实现某种资源池,或者对容器施加边界。
说明:
Semaphore中管理着一组虚拟的许可(Permit),许可的初始数量可通过构造函数来指定,在执行操作时可以首先获得许可,并在使用以后释放许可。
如果没有许可,acquire将阻塞直到有许可(或者直到被中断或者操作超时);
release方法将返回一个许可给信号量;
使用场景:
-
资源池;
-
有界阻塞容器;
4-1、构造函数
4-1-1、默认创建【非公平锁】
/**
* Creates a {@code Semaphore} with the given number of
* permits and nonfair fairness setting.
*
* @param permits the initial number of permits available.
* This value may be negative, in which case releases
* must occur before any acquires will be granted.
*/
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
4-1-2、同步对象-Sync
/**
* Synchronization implementation for semaphore.
* Uses AQS state to represent permits.
* Subclassed into fair and nonfair versions.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
// ... 省略其他方法
4-1-3、非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
// 调用父类Sync的nonfairTryAcquireShared方法
return nonfairTryAcquireShared(acquires);
}
4-1-4、公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
// 调用AQS类的hasQueuedPredecessors方法,队列第一个才能获取锁
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
4-2、获取锁 -- acquire
4-2-1、非公平锁获取
AQS的 acquireSharedInterruptibly 方法
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer {
/**
* Acquires in [shared mode], aborting if interrupted.
* Implemented by first checking interrupt status,
* then invoking at least once {@link #tryAcquireShared},
* returning on success.
* Otherwise the thread is queued,
* possibly repeatedly blocking and unblocking
* invoking {@link #tryAcquireShared} until success or the thread
* is interrupted.
* @param arg the acquire argument.
* This value is conveyed to {@link #tryAcquireShared} but is
* otherwise uninterpreted and can represent anything
* you like.
* @throws InterruptedException if the current thread is interrupted
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared 小于 0,则阻断
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
}
class Semaphore {
private final Sync sync;
public void acquire() throws InterruptedException {
// 构造时用[非公平锁NonfairSync]创建
// 参考AQS的acquireSharedInterruptibly方法
sync.acquireSharedInterruptibly(1);
}
static final class NonfairSync extends Sync {
// 实现AQS的tryAcquireShared方法
@Override
protected int tryAcquireShared(int acquires) {
// 参考父类 Sync 的 nonfairTryAcquireShared 方法
return nonfairTryAcquireShared(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平获取锁
final int nonfairTryAcquireShared(int acquires) {
for (;;) { // 类似[自旋锁]的设计
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
5、FutureTask
FutureTask表示的计算是通过Callable来实现的,相当于可生成结果的Runnable,并且可以处于以下3种状态:
-
**等待运行(**Waiting tp run);
-
正在运行(Running);
-
运行完成(Complete),包括正常结束、由于取消而结束、由于异常而结束;
6、CountDownLatch 和 CyclicBarrier 区别
- CountDownLatch 是不可以重置的,所以无法重用;而 CyclicBarrier 则没有这种限制,可以重用。
- CountDownLatch 的基本操作组合是 countDown/await。调用 await 的线程阻塞等待 countDown 足够的次数,不管你是在一个线程还是多个线程里 countDown,只要次数足够即可。CountDownLatch 操作的是事件。
- CyclicBarrier 的基本操作组合,则就是 await,当所有的伙伴(parties)都调用了 await,才会继续进行任务,并自动进行重置。注意,正常情况下,CyclicBarrier 的重置都是自动发生的,如果我们调用 reset 方法,但还有线程在等待,就会导致等待线程被打扰,抛出 BrokenBarrierException 异常。CyclicBarrier 侧重点是线程,而不是调用事件,它的典型应用场景是用来等待并发线程结束。