深入解析Java AQS:源码剖析与典型实现

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 是一种同步辅助类,它允许一组线程互相等待,直到到达一个共同的屏障点。不同于 CountDownLatchCyclicBarrier 可以重用。

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的源码及其典型实现如 ReentrantLockSemaphoreCountDownLatch 和 CyclicBarrier,我们可以看到AQS如何通过一种通用的机制来实现各种复杂的同步需求。AQS 的设计极其灵活和高效,但其复杂性也要求开发者具备较深的并发编程知识。通过掌握AQS的工作原理,我们可以更好地应用和扩展Java的并发工具,更有效地解决实际中的并发问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值