锁核心类AQS
# 1. AQS概述
# 1.1 AQS是什么?
AQS(AbstractQueuedSynchronizer)是 JUC(java.util.concurrent) 中用于构建同步器(锁、信号量等)的 基础框架,它是 Java并发包的核心组件之一。AQS 基于FIFO等待队列 实现线程的排队,并提供了一套基于 状态(state) 变量的 模板方法 来简化同步器的开发。
AQS 主要用于 构建锁和同步工具,如:
- 独占模式(Exclusive):同一时间只能被一个线程获取,如
ReentrantLock
。 - 共享模式(Shared):多个线程可以同时获取,如
Semaphore
和CountDownLatch
。
核心思想:
- 使用CAS操作 控制共享资源的访问
- 维护一个CLH(FIFO)等待队列 存放获取资源失败的线程
- 使用模板方法模式 让子类实现具体的获取/释放逻辑
# 1.2 AQS的设计目标
AQS 的设计目标主要包括:
- 简化同步器的开发
- 线程同步涉及队列管理、阻塞/唤醒、CAS操作等,AQS 封装了底层逻辑,只需要实现核心方法(
tryAcquire
、tryRelease
等)即可自定义同步器。
- 线程同步涉及队列管理、阻塞/唤醒、CAS操作等,AQS 封装了底层逻辑,只需要实现核心方法(
- 支持两种同步模式
- 独占模式(Exclusive):一次只能被一个线程获取,如
ReentrantLock
。 - 共享模式(Shared):多个线程可同时获取,如
Semaphore
、CountDownLatch
。
- 独占模式(Exclusive):一次只能被一个线程获取,如
- 基于CLH(FIFO)队列 实现同步等待队列
- 线程获取资源失败后会被添加到AQS的队列,按照**先进先出(FIFO)**的方式调度。
- 高效的线程阻塞和唤醒机制
- AQS 通过
LockSupport.park/unpark
高效管理线程阻塞和唤醒,减少CPU空转,提高性能。
- AQS 通过
- 可扩展性 & 可复用性
- AQS 不直接实现锁逻辑,而是提供了 通用的同步框架,具体的同步逻辑由子类(如
ReentrantLock
)来实现。
- AQS 不直接实现锁逻辑,而是提供了 通用的同步框架,具体的同步逻辑由子类(如
# 1.3 AQS的应用场景
AQS 作为 JUC 并发工具的核心,几乎所有的同步类都基于 AQS 实现,包括:
工具类 | 模式 | 作用 |
---|---|---|
ReentrantLock | 独占模式 | 可重入锁,支持公平/非公平模式 |
CountDownLatch | 共享模式 | 计数器,等待多个线程执行完毕 |
Semaphore | 共享模式 | 计数信号量,控制并发线程数 |
ReadWriteLock | 读-共享,写-独占 | 读写分离,提高并发性能 |
CyclicBarrier | 计数同步 | 让一组线程达到某个点后再继续执行 |
Phaser | 多阶段栅栏 | 更灵活的CyclicBarrier |
# 2. AQS的核心数据结构
# 2.1 AQS加锁流程
AQS 的加锁流程主要围绕 同步队列(CLH 队列) 和 条件队列(Condition Queue) 进行,核心机制包括:
- 多个线程竞争锁,只有一个线程成功,其他线程进入 同步队列 排队。
- 持有锁的线程可以释放锁,进入 条件队列 等待某个条件成立时被唤醒。
- 线程唤醒后,重新进入 同步队列,按照 FIFO 规则尝试获取锁。
# 2.2 exclusiveOwnerThread变量
首先AQS继承自AbstractOwnableSynchronizer,其实是为了记录哪个线程正在占用锁,适用于 ReentrantLock
这种独占锁(不适用于 Semaphore
、CountDownLatch
这类共享锁)。。
public abstract class AbstractOwnableSynchronizer {
private transient Thread exclusiveOwnerThread;
// 设置占用锁的线程
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 2.2 State变量(同步状态)
private volatile int state;
AQS 内部维护了一个 volatile int state 变量,用于表示当前同步资源的状态。
- state 变量的值由子类(如 ReentrantLock)决定具体含义。
- 通过 CAS(compareAndSetState) 操作确保对 state 的修改是线程安全的。
- state 变量的操作主要通过以下方法进行:
state 变量的操作方法
方法 | 作用 |
---|---|
getState() | 获取当前同步状态 |
setState(int newState) | 设置同步状态 |
compareAndSetState(int expect, int update) | CAS 方式修改 state(保证原子性) |
示例:ReentrantLock 如何使用 state
- 初始值
state = 0
:表示锁未被占用。 - 线程 A 获取锁(state +1),如果是可重入锁,每重入一次
state +1
。 - 线程 A 释放锁(state -1),如果
state = 0
,表示锁完全释放。
# 2.3 Node 节点结构
在 AQS(AbstractQueuedSynchronizer)中,Node
是 同步队列(CLH 队列) 和 条件队列(Condition Queue) 的核心数据结构。它表示一个线程在等待获取锁的状态,并维护指向前后节点的指针。
虽然同步队列和条件队列都是由Node节点组成的,但是同步队列中是使用prev和next组成双向链表,nextWaiter只用来表示是共享模式还是排他模式。条件队列没有使用到Node中prev和next属性,而是使用nextWaiter组成单链表。
// 继承自AbstractOwnableSynchronizer,为了记录哪个线程占用锁
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
// 同步状态,0表示无锁,每次加锁+1,释放锁-1
private volatile int state;
// 同步队列的头尾节点
private transient volatile Node head;
private transient volatile Node tail;
// Node节点,用来包装线程,放到队列中
static final class Node {
// 节点中的线程
volatile Thread thread;
// 节点状态
volatile int waitStatus;
// 同步队列的前驱节点和后继节点
volatile Node prev;
volatile Node next;
// 条件队列的后继节点
Node nextWaiter;
}
// 条件队列
public class ConditionObject implements Condition {
// 条件队列的头尾节点
private transient Node firstWaiter;
private transient Node lastWaiter;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Node 的关键状态
状态 | 说明 |
---|---|
SIGNAL (-1) | 线程需要被唤醒,表示前驱节点释放锁后,需要唤醒后继线程。 |
CANCELLED (1) | 线程因超时或中断放弃了锁,会被移除队列。 |
CONDITION (-2) | 线程在 Condition.await() 时进入此状态,等待被 signal() 唤醒。 |
PROPAGATE (-3) | 共享模式下的传播状态,表示当前节点释放锁时,需要唤醒多个线程。 |
0 | 默认状态,没有特殊含义。 |
Node 在独占/共享模式下的作用
- 独占模式(Exclusive):线程依次获取锁,如
ReentrantLock
。 - 共享模式(Shared):多个线程同时获取锁,如
Semaphore
。
# 2.4 条件队列(Condition Queue)
概念: 条件队列用于线程等待某些条件时的调度。线程通过 await()
进入条件队列,直到条件满足后通过 signal()
或 signalAll()
唤醒,进入同步队列继续争夺锁。
// 条件队列
public class ConditionObject implements Condition {
// 条件队列的头尾节点
private transient Node firstWaiter;
private transient Node lastWaiter;
}
2
3
4
5
6
# 2.5 FIFO等待队列(CLH队列)
概念: 在 AQS 中,FIFO 等待队列是一种 先进先出(FIFO) 的队列结构,通常用于管理等待获取锁的线程。AQS 使用了一种特殊的队列结构——CLH(Craig, Landin, and Hagersten)队列,来确保线程按顺序获取锁。
CLH队列是一个双向链表结构,用于保证多个线程按顺序获取锁。每个线程都会有一个对应的 Node 节点,且队列的头部表示当前持有锁的线程,队尾表示等待锁的线程。
# 3. AQS的模版方法模式
AQS(AbstractQueuedSynchronizer)提供了一个通用框架来管理线程的同步和锁的访问,支持独占模式和共享模式两种访问资源的方式。AQS本身定义了加锁、释放锁的流程,但并未实现具体的加锁和释放锁的细节,而是通过模板方法模式留给子类(如 ReentrantLock
、CountDownLatch
、Semaphore
、CyclicBarrier
等)去实现具体的行为。
# 3.1 独占模式和共享模式
- 独占模式:只有一个线程能够持有锁,其他线程只能等待。
- 共享模式:多个线程可以同时持有资源,通常用于读写锁等场景。
AQS通过独占模式和共享模式的不同方法来区分这两种锁的行为,下面是每种模式下的方法:
# 3.2 独占模式方法
独占模式是指一个时间点只有一个线程能持有锁,其相关方法包括:
- acquire():用于加锁,尝试获取独占锁,若获取失败,则当前线程会进入等待队列。
- acquireInterruptibly():加锁,但允许中断。如果线程在等待期间被中断,会抛出
InterruptedException
。 - tryAcquireNanos(int arg, long nanosTimeout):尝试在一定时间内获取锁,如果获取失败,则等待指定的时间后返回。
- release():释放独占锁,使得其他线程能够获得该锁。
# 3.3 共享模式方法
共享模式是指多个线程可以同时持有资源的锁,这些线程通常处于读取状态,常见于读写锁等场景。共享模式的方法包括:
- acquireShared():尝试获取共享锁,允许多个线程同时获得锁。
- acquireSharedInterruptibly():加共享锁,但允许中断。如果线程在等待期间被中断,会抛出
InterruptedException
。 - tryAcquireSharedNanos(int arg, long nanosTimeout):尝试在一定时间内获取共享锁,如果失败则等待指定时间。
- releaseShared():释放共享锁,通常是解除对多个线程持有的锁的限制。
# 3.4 AQS中的抽象方法
AQS定义了一些抽象方法,具体的加锁、释放锁逻辑留给子类去实现,这些方法就是模板方法模式的核心。子类需要根据自己的业务需求实现这些方法。AQS提供了以下抽象方法:
- tryAcquire(int arg):尝试获取独占锁,子类实现具体的获取锁逻辑。
- tryRelease(int arg):释放独占锁,子类实现具体的释放锁逻辑。
- tryAcquireShared(int arg):尝试获取共享锁,子类实现具体的共享锁获取逻辑。
- tryReleaseShared(int arg):释放共享锁,子类实现具体的释放共享锁的逻辑。
- isHeldExclusively():判断当前线程是否持有独占锁。
这些方法并没有在AQS中实现,具体的实现交给了继承AQS的子类,子类可以根据自己的需求来实现具体的加锁和释放锁逻辑。
# 3.5 模板方法模式
AQS通过定义一系列的模板方法来规范化加锁和释放锁的流程,这种方式使得AQS本身可以处理线程的排队和等待等公共操作,而具体的锁获取和释放操作则由子类实现。
设计模式:模板方法模式,父类(AQS)定义了加锁、释放锁的整体框架,并在需要的时候调用子类实现的具体方法(如 tryAcquire()
、tryRelease()
、tryAcquireShared()
等)。这样可以保证AQS具有通用性,同时又能提供灵活性,适应不同的同步需求。
我来结合源码讲解AQS (AbstractQueuedSynchronizer) 的工作原理。
# 3. AQS的工作原理
AQS是Java并发包中锁和同步器的基础框架,它通过一个整型状态变量state
和一个FIFO等待队列来实现锁的获取与释放。
# 3.1 获取锁(acquire)
acquire
方法是独占模式获取锁的入口:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
2
3
4
5
执行流程:
- 首先调用
tryAcquire
尝试获取锁,这个方法需要子类实现 - 如果获取失败,创建一个独占模式的节点并加入等待队列
- 调用
acquireQueued
使线程在队列中等待锁 - 如果等待过程中被中断,最后调用
selfInterrupt
恢复中断状态
addWaiter
用于将当前线程包装成Node添加到队列:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 快速尝试在队尾添加
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速添加失败,进入完整版本的入队方法
enq(node);
return node;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
acquireQueued
方法使线程在队列中等待获取锁:
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3.2 释放锁(release)
release
方法用于独占模式下的锁释放:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
2
3
4
5
6
7
8
9
执行流程:
- 调用
tryRelease
尝试释放锁,这个方法需要子类实现 - 如果释放成功,检查头节点的等待状态
- 如果需要,唤醒后继节点的线程
unparkSuccessor
用于唤醒头节点的后继节点:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// 将头节点的状态设为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 找到需要唤醒的节点(通常是下一个节点)
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 如果下一个节点为空或已取消,从尾部开始找
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 唤醒找到的节点线程
if (s != null)
LockSupport.unpark(s.thread);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3.3 共享模式和独占模式
AQS支持两种同步模式:独占模式和共享模式。
独占模式:同一时刻只有一个线程能持有锁,如ReentrantLock。
共享模式:同一时刻可以有多个线程持有锁,如Semaphore、CountDownLatch。
# 共享模式获取锁:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
2
3
4
doAcquireShared
的关键点:
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
共享模式的一个重要特点是锁的传播,setHeadAndPropagate
方法:
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// 如果还有剩余的共享资源,或头节点等待状态表明要传播
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 如果后继节点是共享模式,或者为null(不确定模式),继续唤醒
if (s == null || s.isShared())
doReleaseShared();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 共享模式释放锁:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
2
3
4
5
6
7
doReleaseShared
确保所有等待在共享锁上的线程都能被唤醒:
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // 重新开始循环
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // 重新开始循环
}
// 如果头节点没有变化,退出循环
if (h == head)
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3.4 可中断与超时支持
AQS支持可中断和超时获取锁的方式。
# 可中断获取锁:
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
2
3
4
5
6
doAcquireInterruptibly
的关键在于当检测到中断时直接抛出异常:
private void doAcquireInterruptibly(int arg) throws InterruptedException {
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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 检测到中断直接抛出异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 超时获取锁:
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
2
3
4
5
6
7
doAcquireNanos
增加了超时检查:
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
这就是AQS的核心工作原理,它通过这些机制提供了一套灵活的同步框架,各种同步器如ReentrantLock、Semaphore、CountDownLatch等都是基于这个框架实现的。
# 4. 经典问题与面试高频考点
# 4.1 AQS的底层实现原理
AQS (AbstractQueuedSynchronizer) 的底层实现原理可以总结为以下几点:
状态变量:AQS维护一个volatile整型变量
state
作为同步状态,通过CAS操作和volatile语义保证其可见性和原子性。FIFO队列:使用一个基于双向链表实现的CLH队列来管理等待线程。当线程获取锁失败时,会被封装成Node加入队列尾部。
Node结构:队列节点包含线程引用、等待状态(waitStatus)、前驱和后继指针。waitStatus有多种值:
- CANCELLED (1):线程取消
- SIGNAL (-1):后继节点需要被唤醒
- CONDITION (-2):节点在条件队列中
- PROPAGATE (-3):共享锁需要向后传播
- 0:初始状态
模板方法模式:AQS定义了框架结构,但具体的锁获取与释放逻辑由子类实现:
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }
1
2
3
4
5
6线程阻塞与唤醒:使用
LockSupport.park()
和LockSupport.unpark()
实现线程的阻塞与唤醒,而不是synchronized的monitor机制。CAS操作:使用Unsafe类提供的CAS方法进行非阻塞的原子操作,如修改state、队列节点状态等。
# 4.2 公平锁与非公平锁的区别
公平锁和非公平锁的主要区别在于获取锁的顺序策略:
定义区别:
- 公平锁:严格按照线程在队列中的FIFO顺序获取锁
- 非公平锁:允许新到达的线程在入队前尝试获取锁,可能"插队"
实现区别(以ReentrantLock为例):
// 非公平锁的tryAcquire实现 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 直接尝试获取锁,不检查队列 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 其他代码... } // 公平锁的tryAcquire实现 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && // 检查队列中是否有前驱节点 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 其他代码... }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26性能对比:
- 非公平锁:吞吐量更高,但可能导致某些线程长时间等待(饥饿)
- 公平锁:等待时间更可预测,但有额外开销(需要检查队列)
适用场景:
- 非公平锁:高并发、追求吞吐量的场景
- 公平锁:对线程等待公平性有要求的场景
# 4.3 自旋锁与阻塞锁的对比
自旋锁和阻塞锁代表了两种不同的线程等待策略:
工作原理:
- 自旋锁:线程通过循环检查锁状态,不放弃CPU时间片
- 阻塞锁:线程被挂起(park),放弃CPU时间片
适用场景:
- 自旋锁:适合锁竞争不激烈、锁持有时间短的场景
- 阻塞锁:适合锁竞争激烈、锁持有时间长的场景
优缺点对比:
特性 自旋锁 阻塞锁 CPU消耗 高(一直占用CPU) 低(放弃CPU时间片) 线程切换 无上下文切换 有上下文切换开销 响应速度 快(无需唤醒) 慢(需要唤醒过程) 适用锁持有时间 短 长 系统负载影响 高负载下性能下降明显 高负载下相对稳定 AQS中的应用:AQS结合了两者优势,使用"适应性自旋"策略:
- 先进行一定次数自旋尝试
- 自旋失败后再进入阻塞状态
- 自旋次数会根据历史成功率动态调整
# 4.4 独占模式 vs 共享模式的应用场景
AQS支持两种同步模式,它们适用于不同的场景:
- 独占模式(Exclusive):
- 定义:同一时刻只允许一个线程访问资源
- 实现方法:重写
tryAcquire
和tryRelease
- 典型应用:
- ReentrantLock:独占可重入锁
- WriteLock:读写锁中的写锁
- ThreadPoolExecutor的Worker锁
- 应用场景:需要互斥访问的场景,如数据修改操作
- 共享模式(Shared):
- 定义:同一时刻允许多个线程同时访问资源
- 实现方法:重写
tryAcquireShared
和tryReleaseShared
- 典型应用:
- Semaphore:信号量,控制并发线程数
- CountDownLatch:倒计时门闩,等待多个操作完成
- ReadLock:读写锁中的读锁
- CyclicBarrier:循环栅栏(内部使用ReentrantLock+Condition)
- 应用场景:允许多个线程并发访问的场景,如读操作、资源池限流
- 选择依据:
- 资源是否允许并发访问
- 是否需要控制并发线程数量
- 操作是读密集型还是写密集型
# 4.5 如何自定义一个同步器?
自定义同步器需要继承AQS并实现相应的方法:
- 选择模式:确定是独占模式还是共享模式(或两者都支持)
- 实现基本方法:
- 独占模式:实现
tryAcquire
和tryRelease
- 共享模式:实现
tryAcquireShared
和tryReleaseShared
- 可根据需要同时实现两种模式
- 独占模式:实现
- 定义状态含义:明确AQS中state变量的含义,如:
- 锁计数(如ReentrantLock)
- 可用许可证数量(如Semaphore)
- 等待线程数(如CountDownLatch)
- 设计API:包装AQS方法,提供友好的接口
- 示例:简单的二元信号量实现:
public class BinarySemaphore {
private static final class Sync extends AbstractQueuedSynchronizer {
// 是否可获取,state=1表示可获取,state=0表示已被获取
protected boolean isAvailable() {
return getState() == 1;
}
// 尝试获取:将state从1改为0
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(1, 0)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 释放:将state从0改为1
protected boolean tryRelease(int releases) {
if (getState() == 0) {
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(1);
return true;
}
return false;
}
}
private final Sync sync = new Sync();
// 构造函数,初始为可用状态
public BinarySemaphore() {
sync.setState(1);
}
// 获取许可
public void acquire() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// 释放许可
public void release() {
sync.release(1);
}
// 尝试获取许可(非阻塞)
public boolean tryAcquire() {
return sync.tryAcquire(1);
}
// 当前是否可获取
public boolean isAvailable() {
return sync.isAvailable();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
这个简单的二元信号量示例展示了如何基于AQS创建自定义同步器。在实际应用中,可以根据需求增加超时控制、公平性选项等更多功能。
自定义同步器的关键是正确理解和使用state变量,以及选择合适的同步模式。通过继承AQS,可以轻松获得线程安全、可靠的队列管理和线程调度能力,而不必关心底层的实现细节。