AbstractQueuedSynchronizer (AQS) 是Java并发包(java.util.concurrent
)的核心基础设施,它为锁(Lock)、信号量(Semaphore)、栅栏(CyclicBarrier)、闭锁(CountDownLatch)等多种同步器提供了一个统一的框架。通过解析AQS的源码并深入分析其典型实现,我们可以更好地理解Java并发机制的工作原理。
1. AQS 基础架构与源码分析
1.1 AQS 的核心成员
AQS 的核心包括以下几个关键成员变量和方法:
java
代码解读
复制代码
// 用于表示同步状态的变量 private volatile int state; // 同步队列的头节点 private transient volatile Node head; // 同步队列的尾节点 private transient volatile Node tail; // 尝试获取独占锁,由子类实现 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(); }
state
是AQS的核心变量,它用于表示同步状态。head
和 tail
是一个双向链表结构,用于管理等待的线程。
1.2 线程排队机制
AQS 使用一个双向链表作为同步队列,这个链表的节点是 Node
,每个节点对应一个线程。节点中的关键字段包括:
java
代码解读
复制代码
static final class Node { static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; }
waitStatus
用于表示线程的等待状态,prev
和 next
指向前后节点,形成一个双向链表结构。thread
存放当前节点对应的线程引用。
1.3 获取与释放同步状态
AQS 提供了 acquire
和 release
方法来进行同步状态的获取与释放:
-
acquire 方法的核心逻辑是尝试获取锁(通过调用
tryAcquire
),如果获取失败,则将当前线程加入同步队列中,直到获取成功。scss
代码解读
复制代码
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
-
release 方法则是释放同步状态(调用
tryRelease
),并唤醒同步队列中的下一个节点。java
代码解读
复制代码
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. ReentrantLock 的源码与实现
ReentrantLock
是AQS的一个典型实现,它通过AQS的独占模式实现了可重入锁。ReentrantLock
分为公平锁和非公平锁,分别对应 FairSync
和 NonfairSync
内部类。
2.1 非公平锁的实现
NonfairSync
是 ReentrantLock
的默认实现,它允许“插队”,提高了性能:
java
代码解读
复制代码
static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } 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; } }
lock()
方法首先尝试使用 CAS 直接获取锁,如果失败则通过 acquire(1)
进入 AQS 的获取逻辑。tryAcquire
方法用于尝试获取锁,非公平锁的获取并不考虑队列中的顺序,而是直接尝试抢占。
2.2 公平锁的实现
FairSync
则严格按照FIFO顺序分配锁,避免“插队”:
scala
代码解读
复制代码
static final class FairSync extends Sync { final void lock() { acquire(1); } 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; } }
FairSync
的 tryAcquire
方法在获取锁前,会先检查队列中是否有等待的线程(hasQueuedPredecessors
),确保锁的公平性。
3. Semaphore 的源码与实现
Semaphore
是基于AQS共享模式实现的信号量,允许多个线程同时访问资源。Semaphore
可以控制同时访问的线程数。
3.1 信号量的实现
Semaphore
的主要方法是 acquire
和 release
:
csharp
代码解读
复制代码
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public void release() { sync.releaseShared(1); }
acquire
方法通过 acquireSharedInterruptibly
获取共享资源,而 release
方法通过 releaseShared
释放资源。
3.2 共享模式的实现
tryAcquireShared
和 tryReleaseShared
是 Semaphore
的核心逻辑:
arduino
代码解读
复制代码
protected int tryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } protected boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (compareAndSetState(current, next)) return true; } }
tryAcquireShared
方法在循环中通过CAS尝试减少信号量,当信号量足够时返回剩余的资源数。tryReleaseShared
方法则是增加信号量。
4. CountDownLatch 的源码与实现
CountDownLatch
是一个同步工具类,用于协调一组线程的执行,直到计数器减到零。
4.1 闭锁的实现
CountDownLatch
主要依赖于 await
和 countDown
方法:
csharp
代码解读
复制代码
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public void countDown() { sync.releaseShared(1); }
await
方法会等待计数器减到零,而 countDown
方法则是减少计数器。
4.2 共享模式的实现
CountDownLatch
的 Sync
类实现了 tryAcquireShared
和 tryReleaseShared
方法:
arduino
代码解读
复制代码
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } protected boolean tryReleaseShared(int releases) { for (;;) { int c = getState(); if (c == 0) return false; int nextc = c - 1; if (compareAndSetState(c, nextc)) return nextc == 0; } }
tryAcquireShared
在 await
时检查计数器是否为零,如果为零则可以通过。tryReleaseShared
则减少计数器,当减到零时唤醒等待的线程。
5. CyclicBarrier 的源码与实现
CyclicBarrier
是一种同步辅助类,它允许一组线程互相等待,直到到达一个共同的屏障点。不同于 CountDownLatch
,CyclicBarrier
可以重用。
5.1 栅栏的实现
CyclicBarrier
的核心方法是 await
:
csharp
代码解读
复制代码
public int await() throws InterruptedException, BrokenBarrierException { return dowait(false, 0L); }
5.2 屏障的管理
CyclicBarrier
使用了一个计数器和一个屏障点来管理线程的等待和唤醒:
csharp
代码解读
复制代码
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(); int index = --count; if (index == 0) { // tripped nextGeneration(); return 0; } for (;;) { try { if (!timed) trip.await(); else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { breakBarrier(); throw ie; } if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; } } finally { lock.unlock(); } }
当最后一个线程到达时,屏障会重置,并唤醒所有等待的线程。否则,线程进入等待状态,直到屏障被触发。
6. 总结
通过深入剖析AQS的源码及其典型实现如 ReentrantLock
、Semaphore
、CountDownLatch
和 CyclicBarrier
,我们可以看到AQS如何通过一种通用的机制来实现各种复杂的同步需求。AQS 的设计极其灵活和高效,但其复杂性也要求开发者具备较深的并发编程知识。通过掌握AQS的工作原理,我们可以更好地应用和扩展Java的并发工具,更有效地解决实际中的并发问题。