1. 管程(Monitor):Java并发设计的基石
1.1 管程的本质与核心思想
管程(Monitor)是操作系统和编程语言中管理共享资源访问的一种高级同步机制。在Java并发编程中,管程的概念被广泛应用,它本质上是一个管理共享变量以及对共享变量操作过程的机制。
管程的核心思想包含两个关键方面:
-
互斥性:同一时刻只允许一个线程访问共享资源,这是通过内置的锁机制实现的。当一个线程进入管程时,它会自动获取锁;当它离开时,会自动释放锁。
-
协作性:管程提供了条件变量(Condition Variables)机制,允许线程在某些条件不满足时暂时放弃锁并等待,直到其他线程改变了条件并发出通知。
注意:Java中的synchronized关键字和Object的wait()/notify()机制实际上就是管程的一种实现方式。而AQS(AbstractQueuedSynchronizer)则是另一种更灵活、更强大的管程实现。
1.2 管程的三种经典模型
在并发编程理论中,管程有三种主要的实现模型,它们在通知机制和线程调度策略上有所不同:
| 模型类型 | 通知时机 | 线程切换策略 | Java实现参考 |
|---|---|---|---|
| Hasen模型 | notify()必须放在代码最后 | 立即切换线程 | 无直接对应 |
| Hoare模型 | notify()后立即切换线程 | 被通知线程立即执行 | 无直接对应 |
| MESA模型 | notify()仅将线程移入入口队列 | 当前线程继续执行,被通知线程稍后执行 | synchronized和AQS都采用 |
Java的管程实现主要基于MESA模型,但做了一些简化和调整:
- 在synchronized内置锁中,只支持一个条件变量(即Object本身的wait/notify)
- 在AQS中,通过ConditionObject支持多个条件变量
- 通知后的线程调度策略遵循MESA模型的特点
1.3 Java中的管程实现对比
Java提供了两种主要的管程实现方式:
java复制// 方式1:基于Object的内置管程(synchronized)
public synchronized void method() {
while (!condition) {
wait(); // 使用Object的wait/notify
}
// 执行操作
notifyAll();
}
// 方式2:基于AQS的显式管程(Lock+Condition)
Lock lock = new ReentrantLock();
Condition cond = lock.newCondition();
public void method() {
lock.lock();
try {
while (!condition) {
cond.await(); // 使用Condition的await/signal
}
// 执行操作
cond.signalAll();
} finally {
lock.unlock();
}
}
两种实现的关键区别:
- 灵活性:AQS支持多个条件变量,而synchronized只支持一个
- 功能:AQS提供了更丰富的功能,如可中断、超时、公平性选择等
- 性能:在低竞争情况下,synchronized有JVM优化优势;高竞争时AQS通常表现更好
- 使用方式:synchronized更简洁,AQS需要显式的加锁解锁
1.4 Condition使用深度解析
Condition接口提供了类似Object监视器方法(wait、notify等)的功能,但与Lock配合使用可以实现更精细的线程控制。下面是一个更复杂的Condition使用示例:
java复制public class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition(); // 条件:缓冲区未满
final Condition notEmpty = lock.newCondition(); // 条件:缓冲区非空
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 等待"未满"条件
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal(); // 通知"非空"条件
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 等待"非空"条件
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal(); // 通知"未满"条件
return x;
} finally {
lock.unlock();
}
}
}
这个有界缓冲区示例展示了如何用两个Condition实现精确的线程通知:
- 当缓冲区满时,put操作会等待notFull条件
- 当take操作取出元素后,会通知notFull条件
- 当缓冲区空时,take操作会等待notEmpty条件
- 当put操作放入元素后,会通知notEmpty条件
关键点:与Object的wait/notify不同,Condition的await/signal必须与Lock配合使用,且在调用这些方法前必须持有锁。此外,条件检查应该使用while循环而不是if语句,以防止虚假唤醒。
2. AQS深度解析:Java并发核心框架
2.1 AQS架构设计总览
AbstractQueuedSynchronizer(AQS)是Java并发包中的核心框架,它为实现各种同步器提供了基础实现。JUC中的大多数同步器(如ReentrantLock、Semaphore、CountDownLatch等)都是基于AQS构建的。
AQS的核心设计采用了模板方法模式:
-
框架部分:AQS提供了同步器的核心框架,包括:
- 同步状态管理(state)
- 线程阻塞/唤醒机制
- 队列管理(CLH队列)
-
可扩展部分:子类通过实现特定方法来自定义同步行为:
- tryAcquire/tryRelease:独占模式
- tryAcquireShared/tryReleaseShared:共享模式
- isHeldExclusively:是否被当前线程独占
AQS的类结构简化如下:
java复制public abstract class AbstractQueuedSynchronizer {
// 同步状态
private volatile int state;
// 等待队列节点
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
// 队列头尾指针
private transient volatile Node head;
private transient volatile Node tail;
// 需要子类实现的方法
protected boolean tryAcquire(int arg) { ... }
protected boolean tryRelease(int arg) { ... }
// ...其他protected方法
// 提供给外部的public方法
public final void acquire(int arg) { ... }
public final boolean release(int arg) { ... }
// ...其他public方法
}
2.2 同步状态(state)的精妙设计
AQS中的state字段是一个volatile int变量,它表示同步器的状态。不同的同步器对state的解释不同:
-
ReentrantLock:state表示锁的重入次数
- 0:锁未被占用
- 1:锁被占用,无重入
-
1:锁被占用,且重入了n-1次
-
Semaphore:state表示剩余的许可数量
- 初始化时state=permits(许可数)
- 每acquire一个许可,state减1
- 每release一个许可,state加1
-
CountDownLatch:state表示剩余的计数
- 初始化时state=count
- 每countDown一次,state减1
- 当state=0时,所有等待线程被唤醒
AQS提供了三种原子操作来访问state:
java复制// 获取当前状态
protected final int getState() {
return state;
}
// 设置状态值
protected final void setState(int newState) {
state = newState;
}
// CAS原子更新状态
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
设计要点:state被设计为volatile保证可见性,而compareAndSetState使用CAS保证原子性。这种组合既保证了线程安全,又避免了重量级锁的开销。
2.3 CLH队列的变体实现
AQS使用了一种变体的CLH队列来管理等待线程。CLH原本是Craig、Landin和Hagersten三位作者提出的一种自旋锁队列,AQS对其进行了改造:
-
节点结构:每个等待线程被封装为一个Node对象,包含:
- thread:等待的线程
- waitStatus:节点状态(CANCELLED、SIGNAL等)
- prev/next:前驱和后继指针
- nextWaiter:用于条件队列链接
-
队列管理:
- 队列是双向链表,有head和tail指针
- 入队通过CAS操作保证线程安全
- 出队(获取锁)时,头节点的线程获取锁
-
状态传播:
- 节点的waitStatus用于控制阻塞和唤醒
- SIGNAL(-1)表示后继节点需要被唤醒
- CANCELLED(1)表示节点已取消
CLH队列在AQS中的工作流程:
- 线程获取锁失败时,创建Node并加入队列尾部
- 前驱节点的waitStatus设置为SIGNAL,然后park当前线程
- 锁释放时,从队列头部开始查找第一个未取消的节点,unpark其线程
- 被唤醒的线程尝试获取锁,成功后成为新的头节点
java复制// 添加等待节点到队列
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); // 队列为空或CAS失败时,进入enq
return node;
}
// 入队辅助方法(处理初始化或竞争情况)
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 初始化队列
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
2.4 条件队列的实现机制
AQS中的条件队列与同步队列(CLH队列)是分开管理的:
-
数据结构:
- 条件队列是单向链表,通过nextWaiter连接
- 每个ConditionObject维护一个独立的条件队列
- 节点从同步队列转移到条件队列(await时)
- 节点从条件队列转移回同步队列(signal时)
-
await操作流程:
- 线程调用await()时,释放锁
- 创建新的Node(状态为CONDITION)加入条件队列
- 完全释放锁(处理可重入情况)
- park当前线程,进入等待状态
-
signal操作流程:
- 将条件队列的第一个节点转移到同步队列
- 节点状态从CONDITION改为0
- 如果前驱节点已取消或设置SIGNAL失败,唤醒线程让其自己处理
java复制// await()的核心实现
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // 加入条件队列
int savedState = fullyRelease(node); // 完全释放锁
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 挂起线程
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 被唤醒后尝试重新获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清理取消的节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
}
// signal()的核心实现
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); // 转移第一个节点到同步队列
}
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && // 转移节点
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node); // 加入同步队列
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 唤醒线程
return true;
}
关键点:条件队列和同步队列的节点是同一个Node类的实例,只是通过waitStatus和nextWaiter字段区分其用途。这种设计既节省了内存,又简化了节点在两种队列间转移的实现。
3. ReentrantLock源码深度剖析
3.1 ReentrantLock整体架构设计
ReentrantLock是AQS最典型的实现之一,它的类层次结构体现了良好的设计:
java复制// ReentrantLock类结构
public class ReentrantLock implements Lock, Serializable {
private final Sync sync; // 同步器实例
abstract static class Sync extends AbstractQueuedSynchronizer {
// 实现AQS的tryRelease等方法
abstract void lock(); // 由子类实现具体锁策略
}
// 非公平锁实现
static final class NonfairSync extends Sync {
final void lock() { ... }
protected final boolean tryAcquire(int acquires) { ... }
}
// 公平锁实现
static final class FairSync extends Sync {
final void lock() { ... }
protected final boolean tryAcquire(int acquires) { ... }
}
// 构造方法
public ReentrantLock() { sync = new NonfairSync(); } // 默认非公平
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
// Lock接口方法实现
public void lock() { sync.lock(); }
public void unlock() { sync.release(1); }
// ...其他方法实现
}
设计特点:
- 策略模式:通过Sync抽象类和两个具体子类(FairSync/NonfairSync)实现不同的锁策略
- 模板方法:Sync继承AQS,实现部分方法,子类实现剩余方法
- 可插拔策略:通过构造方法选择公平或非公平策略
- 可重入支持:通过state记录锁的持有计数
3.2 加锁流程全解析
3.2.1 非公平锁的加锁过程
非公平锁的加锁流程体现了"插队"策略:
java复制// NonfairSync.lock()
final void lock() {
// 第一步:直接尝试CAS获取锁(插队行为)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 失败后走正常流程
}
// AQS.acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 再次尝试获取
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 加入队列并等待
selfInterrupt(); // 恢复中断状态
}
// NonfairSync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// Sync.nonfairTryAcquire()
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { // 锁空闲
if (compareAndSetState(0, acquires)) { // 直接尝试获取
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 重入
int nextc = c + acquires;
if (nextc < 0) // 溢出检查
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
非公平锁的特点:
- 新线程可以直接插队尝试获取锁,不考虑队列中是否有等待线程
- 这种策略减少了线程挂起和唤醒的开销,提高了吞吐量
- 但可能导致某些线程长时间获取不到锁(饥饿)
3.2.2 公平锁的加锁过程
公平锁严格遵循FIFO原则:
java复制// FairSync.lock()
final void lock() {
acquire(1); // 直接走AQS流程,不尝试插队
}
// FairSync.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;
}
}
else if (current == getExclusiveOwnerThread()) { // 重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// AQS.hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t && // 队列不为空
((s = h.next) == null || s.thread != Thread.currentThread());
}
公平锁的特点:
- 新线程必须检查队列中是否有等待线程
- 只有队列为空或当前线程是队列头节点时才能获取锁
- 保证了严格的FIFO顺序,避免了饥饿现象
- 但增加了上下文切换开销,吞吐量通常低于非公平锁
3.3 解锁流程全解析
解锁流程在公平锁和非公平锁中是相同的:
java复制// ReentrantLock.unlock()
public void unlock() {
sync.release(1);
}
// AQS.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;
}
// Sync.tryRelease()
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 完全释放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// AQS.unparkSuccessor()
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
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); // 唤醒线程
}
解锁流程的关键点:
- 只有锁的持有者才能释放锁
- 可重入锁需要完全释放(state=0)才会真正解锁
- 解锁后会唤醒队列中的下一个等待线程
- 从尾向前的遍历是为了处理并发修改的情况
3.4 条件变量的实现机制
ReentrantLock通过内部类ConditionObject实现条件变量:
java复制public class ConditionObject implements Condition {
private transient Node firstWaiter; // 条件队列头
private transient Node lastWaiter; // 条件队列尾
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // 加入条件队列
int savedState = fullyRelease(node); // 完全释放锁
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 挂起线程
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 被唤醒后尝试重新获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清理取消的节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
}
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); // 转移第一个节点到同步队列
}
// ...其他方法实现
}
条件变量的关键实现细节:
- 每个ConditionObject维护一个独立的条件队列
- await()会将线程从同步队列转移到条件队列
- signal()会将线程从条件队列转移回同步队列
- 条件队列使用nextWaiter字段链接,是单向链表
- 转移节点时会保持中断状态和等待状态
4. 公平锁与非公平锁的深度对比
4.1 实现差异的根源分析
公平锁与非公平锁的核心区别在于获取锁的策略:
-
非公平锁:
- 新线程可以直接尝试获取锁,不考虑队列中是否有等待线程
- 体现在两个地方:
- lock()方法中直接尝试CAS
- tryAcquire()中不检查hasQueuedPredecessors()
-
公平锁:
- 新线程必须检查队列中是否有等待线程
- 只有队列为空或当前线程是头节点时才能获取锁
- 严格遵循FIFO原则
代码层面的关键差异点:
java复制// 非公平锁的tryAcquire实现
final boolean nonfairTryAcquire(int acquires) {
// ...
if (c == 0) {
if (compareAndSetState(0, acquires)) { // 直接尝试CAS
setExclusiveOwnerThread(current);
return true;
}
}
// ...
}
// 公平锁的tryAcquire实现
protected final boolean tryAcquire(int acquires) {
// ...
if (c == 0) {
if (!hasQueuedPredecessors() && // 检查是否有前驱节点
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ...
}
4.2 性能对比与量化分析
公平锁与非公平锁的性能差异主要来自线程调度的开销:
-
非公平锁的优势:
- 减少了线程挂起和唤醒的次数
- 新线程可以直接获取锁,减少了上下文切换
- 在高竞争环境下吞吐量更高
-
公平锁的优势:
- 保证了严格的FIFO顺序
- 避免了线程饥饿现象
- 响应时间更可预测
性能测试示例:
java复制public class FairVsNonfairBenchmark {
private static final int THREADS = 10;
private static final int ITERATIONS = 100000;
public static void main(String[] args) {
testLock(new ReentrantLock(true), "公平锁");
testLock(new ReentrantLock(false), "非公平锁");
}
private static void testLock(ReentrantLock lock, String label) {
long start = System.nanoTime();
ExecutorService executor = Executors.newFixedThreadPool(THREADS);
for (int i = 0; i < THREADS; i++) {
executor.submit(() -> {
for (int j = 0; j < ITERATIONS; j++) {
lock.lock();
try {
// 模拟临界区操作
Thread.sleep(1);
} finally {
lock.unlock();
}
}
});
}
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
long duration = System.nanoTime() - start;
System.out.printf("%s: %.2f ops/ms%n",
label, (ITERATIONS * THREADS) / (duration / 1_000_000.0));
}
}
典型测试结果(仅供参考):
| 锁类型 | 吞吐量(ops/ms) | 相对性能 |
|---|---|---|
| 非公平锁 | 850.32 | 基准 |
| 公平锁 | 620.15 | ~73% |
4.3 实际应用场景建议
根据不同的应用场景选择合适的锁策略:
适合使用非公平锁的场景:
- 锁持有时间较短(<100μs)
- 线程间竞争不激烈
- 对吞吐量要求高于公平性
- 允许偶尔的线程饥饿
适合使用公平锁的场景:
- 锁持有时间较长(>1ms)
- 对响应时间的公平性有严格要求
- 需要避免线程饥饿
- 系统负载相对稳定
经验法则:在大多数情况下,非公平锁的性能优势更明显。只有在确实需要严格公平性的场景下才使用公平锁,因为其性能开销可能高达30%。
4.4 常见误区与最佳实践
误区1:认为公平锁总是更"公平"
实际上,公平锁的公平性是有代价的。在以下情况可能适得其反:
- 当锁释放时正好有新线程到达,公平锁会强制新线程排队
- 这种严格的排序可能导致CPU缓存利用率降低
- 增加了不必要的上下文切换
误区2:忽视锁的可重入特性
java复制// 错误示例:不必要的嵌套锁
lock.lock();
try {
if (condition) {
lock.lock(); // 实际上不需要
try {
// ...
} finally {
lock.unlock();
}
}
} finally {
lock.unlock();
}
// 正确做法:利用可重入特性
lock.lock();
try {
if (condition) {
// 直接访问,无需再次加锁
}
} finally {
lock.unlock();
}
最佳实践建议:
- 尽量缩小临界区范围
- 考虑使用tryLock()避免死锁
- 对于复杂操作,考虑使用读写锁(ReentrantReadWriteLock)
- 在高竞争环境下,考虑使用分段锁或乐观锁
- 使用适当的锁超时机制
java复制// 使用tryLock避免死锁
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// 访问共享资源
} finally {
lock.unlock();
}
} else {
// 执行备选方案
log.warn("获取锁超时,执行备选逻辑");
}
5. AQS的设计哲学与扩展实践
5.1 模板方法模式的应用
AQS是模板方法模式的经典实现,它定义了同步器的骨架,而将特定步骤的实现延迟到子类:
java复制// AQS中的模板方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 由子类实现
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 需要子类实现的方法(protected)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
这种设计的好处:
- 代码复用:AQS提供了完整的同步器框架,包括队列管理、阻塞唤醒等复杂逻辑
- 扩展性:子类只需关注资源获取和释放的逻辑
- 一致性:所有基于AQS的同步器都有相同的行为模式
5.2 自定义同步器实践
基于AQS实现一个简单的二元闭锁(类似CountDownLatch(1)):
java复制public class OneShotLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
protected int tryAcquireShared(int acquires) {
// 只有state=1时才能获取成功
return (getState() == 1) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// 释放锁,设置state=1
setState(1);
return true;
}
}
private final Sync sync = new Sync();
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void signal() {
sync.releaseShared(1);
}
}
这个实现展示了AQS的共享模式使用:
- 初始state=0,所有await线程被阻塞
- 调用signal()将state设为1,唤醒所有等待线程
- 之后await的线程会直接通过
5.3 性能优化技巧
基于AQS实现高性能同步器时的一些优化技巧:
-
减少CAS竞争:
- 使用多个AQS实例进行锁分解
- 考虑使用自旋-等待策略减少阻塞
-
状态设计:
- 合理利用state的32位空间
- 对于复杂状态,可以使用位运算分割
-
避免不必要的阻塞:
- 实现tryLock/tryAcquire等非阻塞方法
- 提供超时机制
-
条件通知优化:
- 精确控制signal和signalAll的使用
- 避免不必要的线程唤醒
java复制// 高性能同步器示例:简单的自旋锁
public class SpinLock {
private static final class Sync extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int acquires) {
// 自旋尝试获取锁
int spins = 0;
while (true) {
int s = getState();
if (s != 0) {
if (spins++ > 1000) // 自旋一定次数后放弃
return false;
Thread.onSpinWait();
continue;
}
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
}
protected boolean tryRelease(int releases) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
5.4 AQS的适用场景与限制
AQS非常适合实现以下类型的同步器:
- 互斥锁:如ReentrantLock
- 资源计数:如Semaphore
- 屏障:如CountDownLatch、CyclicBarrier
- 条件等待:如BlockingQueue的实现
然而,AQS也有其局限性:
- 不适合大量读少量写的场景:此时ReadWriteLock可能更合适
- 状态空间有限:state只有32位
- 无法实现某些复杂策略:如优先级调度
经验分享:在考虑使用AQS前,先检查JUC中是否已有合适的同步器。大多数常见场景(如锁、信号量、屏障等)都有现成实现,只有特殊需求才需要自定义AQS同步器。
6. ReentrantLock高级特性与实战技巧
6.1 可中断锁获取
ReentrantLock提供了可中断的锁获取机制,这是synchronized不具备的特性:
java复制public void performTaskWithLock() throws InterruptedException {
lock.lockInterruptibly(); // 可中断的加锁
try {
while (condition) {
condition.await(); // 可中断的等待
}
// 执行任务
} finally {
lock.unlock();
}
}
关键优势:
- 允许外部中断长时间等待锁的线程
- 提供了更灵活的线程控制手段
- 适合实现可取消的任务
6.2 限时锁等待
tryLock方法支持带超时的锁获取,可以有效避免死锁:
java复制public boolean tryPerformTask(long timeout, TimeUnit unit) {
try {
if (!lock.tryLock(timeout, unit)) {
return false; // 超时未获取锁
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
try {
// 执行任务
return true;
} finally {
lock.unlock();
}
}
使用场景:
- 避免死锁:当多个锁需要按不同顺序获取时
- 实时系统:保证任务在限定时间内完成
- 优雅降级:当无法获取锁时执行备选方案
6.3 条件变量的高级用法
ReentrantLock的条件变量支持更精细的线程控制:
java复制class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 等待"不满"信号
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal(); // 发送"非空"信号
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 等待"非空"信号
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal(); // 发送"不满"信号
return x;
} finally {
lock.unlock();
}
}
}
高级技巧:
- 使用多个条件变量分离不同的等待条件
- signal()比signalAll()更高效,但使用要谨慎
- 总是使用while循环检查条件,避免虚假唤醒
6.4 锁的监控与调试
ReentrantLock提供了一些监控方法,可用于调试:
java复制//