news 2026/7/27 1:06:42

Java 并发锁体系全解:从 synchronized、Lock 到 AQS 底层原理与源码深度剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 并发锁体系全解:从 synchronized、Lock 到 AQS 底层原理与源码深度剖析

在 Java 并发编程体系中,锁是解决多线程共享资源竞争、保证线程安全的核心机制。从 JVM 内置的synchronized隐式锁,到 JUC 包提供的Lock显式锁,再到支撑整个并发工具集的底层基石 AbstractQueuedSynchronizer(AQS),构成了一套层层递进、设计精妙的锁实现体系。

本文将从上层 API 到底层源码,完整拆解 Java 锁的核心原理,覆盖独占模式与共享模式、公平锁与非公平锁、条件队列与双队列联动、读写锁实现等核心知识点,结合 JDK 源码逐层剖析其设计思想与执行流程。

一、Java 锁的两类核心实现:synchronized 与 Lock

1.1 synchronized:JVM 内置隐式锁

synchronized是 JVM 层面实现的内置互斥锁,核心特性是隐式获取与释放锁,无需开发者手动干预:

  • 进入同步代码块 / 同步方法时,JVM 自动为当前线程加锁;代码正常执行完成或抛出异常时,自动释放锁
  • 底层依托对象头的 Mark Word 实现,内置偏向锁、轻量级锁、重量级锁的自适应升级机制
  • 局限性突出:不支持响应中断、不支持超时获取、仅能绑定一个等待条件,无法适配复杂的并发协作场景

1.2 Lock:API 层显式锁的灵活控制

Lockjava.util.concurrent.locks包下的顶层接口,属于 API 层面的显式锁,需要手动调用方法完成锁的获取与释放,相比synchronized具备更强的可操作性与场景适配能力:

  • 支持可中断获取锁:等待过程中可以响应线程中断,终止等待
  • 支持超时获取锁:在指定时长内尝试获取锁,超时自动放弃,避免永久阻塞
  • 支持绑定多个条件变量,可实现更精细的等待 / 通知逻辑
  • 支持公平锁与非公平锁模式切换,可根据业务场景选择调度策略

Lock 接口的核心方法如下:

  • lock():阻塞式获取锁,获取成功前持续等待,不响应中断
  • unlock():释放锁,必须手动调用,通常放在finally块中确保执行
  • tryLock():非阻塞尝试获取锁,立即返回布尔结果,不阻塞线程
  • lockInterruptibly():可中断式获取锁,等待过程中响应中断并抛出异常
  • newCondition():创建绑定当前锁的Condition条件变量,一个锁可绑定多个独立条件

JUC 中 Lock 的核心实现类ReentrantLock,以及读写锁、信号量等工具,其底层全部基于 AQS 框架实现。

二、AQS:并发同步器的核心基石

AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包中绝大多数同步工具(ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 等)的底层实现框架,它定义了一套多线程访问共享资源的通用同步机制,通过模板方法模式将通用逻辑封装,子类仅需实现少量业务方法即可定制同步器。

2.1 核心组件 1:volatile 修饰的同步状态 state

AQS 通过一个被volatile修饰的整型变量state表示同步状态,这是整个同步器的核心状态变量:

private volatile int state;
  • 独占模式下:通常state=0代表锁未被占用,state=1代表锁已被持有,可重入场景下 state 随重入次数递增
  • 共享模式下:state 代表可用的共享资源总数

volatile保证了 state 在多线程间的可见性,AQS 同时提供compareAndSetState(int expect, int update)方法,基于 CAS 操作保证 state 修改的原子性,避免多线程并发修改的线程安全问题。

2.2 核心组件 2:FIFO 双向同步队列

当线程获取同步状态失败时,AQS 会将线程封装为Node节点,加入一个FIFO(先进先出)的双向链表同步队列,通过队列实现线程的等待调度与唤醒管理。

队列的节点为 AQS 的内部类Node,核心字段如下:

static final class Node { // 节点等待状态 volatile int waitStatus; // 前驱节点指针 volatile Node prev; // 后继节点指针 volatile Node next; // 节点绑定的等待线程 volatile Thread thread; // 条件队列后继节点,区分独占/共享模式 Node nextWaiter; }
  • 队列结构:双向链表,包含头节点head和尾节点tail,头节点代表当前持有同步状态的节点,后续节点按入队顺序等待
  • 节点模式:分为独占模式Node.EXCLUSIVE和共享模式Node.SHARED,通过nextWaiter字段区分

三、AQS 独占式同步状态的获取与释放

独占模式的核心特性是:同一时间只能有一个线程持有同步状态,对应 ReentrantLock 等互斥锁的实现。

3.1 独占式获取:acquire () 源码全解析

acquire(int arg)是独占模式下获取同步状态的入口方法,也是lock()方法的底层核心:

public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

执行流程分为四步:

  1. 尝试快速获取:调用tryAcquire(arg)尝试直接获取同步状态,成功则方法直接返回
  2. 节点封装入队:获取失败时,调用addWaiter将当前线程封装为独占模式节点,加入同步队列尾部
  3. 队列自旋等待:调用acquireQueued,让节点在队列中自旋等待,直到获取到同步状态
  4. 中断标记补全:若等待过程中线程被中断过,最后调用selfInterrupt()补上中断标记

分步核心源码解析

  1. tryAcquire:子类实现的获取逻辑AQS 本身不实现具体的获取逻辑,交由子类根据业务特性重写,是模板方法模式的核心体现:

    protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
    }

    例如 ReentrantLock 的公平锁与非公平锁,就在该方法中实现了不同的抢占规则。
  2. addWaiter:节点加入同步队列将当前线程封装为 Node 节点,先通过 CAS 快速尝试加入队尾,失败则进入自旋入队逻辑:
    private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // 快速尝试:CAS直接设置队尾
    Node pred = tail;
    if (pred != null) {
    node.prev = pred;
    if (compareAndSetTail(pred, node)) {
    pred.next = node;
    return node;
    }
    }
    // 快速尝试失败,进入自旋+CAS的enq方法
    enq(node);
    return node;
    }

    3.enq:自旋保证节点安全入队通过死循环 + CAS,保证高并发下节点一定能成功入队,同时完成队列的懒初始化:
    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;
    }
    }
    }
    }

    4.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; // 帮助GC回收旧头节点
    failed = false;
    return interrupted;
    }
    // 获取失败,判断是否需要阻塞当前线程
    if (shouldParkAfterFailedAcquire(p, node) &&
    parkAndCheckInterrupt())
    interrupted = true;
    }
    } finally {
    if (failed)
    cancelAcquire(node);
    }
    }

    3.2 waitStatus 节点等待状态详解
    Node 节点的waitStatus字段控制着线程的等待状态与唤醒逻辑,JDK 1.8 中共有 5 种取值:
状态值常量名含义说明
0-初始状态,节点刚创建入队时的默认状态
-1SIGNAL当前节点的后继节点处于阻塞状态,当前节点释放同步状态或取消时,必须唤醒后继节点
1CANCELLED线程因超时、中断等原因取消等待,节点作废,不再参与同步竞争
-2CONDITION节点位于 Condition 条件队列中,等待条件满足,此时不在同步队列内
-3PROPAGATE共享模式下,唤醒操作需要向后传播,确保所有等待的共享节点都能被通知

其中shouldParkAfterFailedAcquire方法是状态流转的核心,负责调整前驱节点状态并判断当前线程是否可以安全阻塞:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; // 前驱已设为SIGNAL,当前线程可以安全阻塞 if (ws == Node.SIGNAL) return true; // 前驱节点已取消,向前跳过所有取消节点,重新链接队列 if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 前驱为初始状态或PROPAGATE,CAS修改为SIGNAL compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }

3.3 正常流程与取消流程的状态流转

正常流程下的状态流转

在无取消、无异常的正常竞争场景下,节点的状态流转如下:

  1. 节点入队:新创建的节点 waitStatus 为初始值 0,加入队列尾部
  2. 状态修改:节点执行shouldParkAfterFailedAcquire,将前驱节点的 waitStatus 从 0 CAS 修改为 SIGNAL (-1)
  3. 线程阻塞:下一轮自旋再次尝试获取失败后,确认前驱状态为 SIGNAL,调用LockSupport.park()阻塞当前线程
  4. 被唤醒:头节点释放同步状态时,唤醒后继节点,当前线程从 park 中苏醒
  5. 获取成功:线程再次尝试获取同步状态,成功后将自己设为新的头节点,旧头节点出队,完成一次状态流转

取消流程下的状态流转

当线程等待过程中发生异常、中断或超时,会触发节点取消流程,核心逻辑在cancelAcquire方法中:

private void cancelAcquire(Node node) { if (node == null) return; node.thread = null; // 向前遍历,跳过所有已取消的前驱节点 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; Node predNext = pred.next; // 将当前节点状态设为取消 node.waitStatus = Node.CANCELLED; // 情况1:当前节点是尾节点,更新队尾指针 if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { int ws; // 情况2:当前节点不是头节点的后继,将前驱与后继节点链接 if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { // 情况3:当前节点是头节点的后继,直接唤醒后继节点 unparkSuccessor(node); } node.next = node; // 帮助GC } }

取消流程的核心是:将节点标记为 CANCELLED,调整队列双向指针,把取消节点从同步队列中剔除,保证队列的有效性。

3.4 独占式同步状态释放

release(int arg)是独占模式下释放同步状态的入口方法:

public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; // 头节点不为空且状态非初始值,需要唤醒后继 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }

执行流程:

  1. 调用tryRelease(arg)尝试释放同步状态,由子类实现具体逻辑,释放成功返回 true
  2. 释放成功后,检查头节点状态:若头节点存在且 waitStatus 不为 0,调用unparkSuccessor唤醒后继等待线程

唤醒后继节点的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); }

为什么从队尾往前找后继节点?因为节点入队时先设置 prev 指针、再 CAS 更新 tail、最后设置前驱的 next 指针,从后往前遍历能保证不会漏掉刚入队的节点,避免空指针问题。

四、ReentrantLock:公平锁与非公平锁源码对比

ReentrantLock 是 AQS 独占模式最经典的实现,也是可重入互斥锁的标准实现。它通过内部两个 AQS 子类,实现了公平锁(FairSync)非公平锁(NonfairSync)两种模式,二者的核心差异完全体现在对tryAcquire方法的不同实现上。

4.1 ReentrantLock 整体类结构

ReentrantLock内部维护了一个继承自 AQS 的抽象内部类Sync,作为公共逻辑基类;再由FairSyncNonfairSync两个子类分别实现公平与非公平策略。

public class ReentrantLock implements Lock, java.io.Serializable { // 同步器基类,继承AQS abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); // 公共非公平获取逻辑,供非公平锁直接调用 final boolean nonfairTryAcquire(int acquires) { ... } } // 非公平锁实现 static final class NonfairSync extends Sync { ... } // 公平锁实现 static final class FairSync extends Sync { ... } // 默认构造:非公平锁 public ReentrantLock() { sync = new NonfairSync(); } // 传入true指定公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } }

可重入特性:两种锁都支持重入,即同一个线程可以多次获取同一把锁,state值随重入次数递增,释放时对应递减,直到 state 归 0 才算完全释放锁。重入逻辑在两种模式下完全一致。

4.2 非公平锁 NonfairSync 源码解析

非公平锁是ReentrantLock的默认实现,核心特点是:线程获取锁时不考虑队列中是否有等待线程,直接尝试抢占,抢占失败才进入队列排队

lock () 入口:上来就插队

非公平锁的lock()方法,在进入 AQS 的acquire流程之前,会先通过 CAS 直接尝试抢一次锁,这是第一次插队机会。

final void lock() { // 第一次插队:直接CAS尝试把state从0改成1 if (compareAndSetState(0, 1)) // 抢锁成功,设置当前线程为锁的持有者 setExclusiveOwnerThread(Thread.currentThread()); else // 抢占失败,走AQS标准的acquire获取流程 acquire(1); }

tryAcquire:再次插队,不判断队列

非公平锁的tryAcquire直接调用基类的nonfairTryAcquire,核心逻辑是:只要锁空闲(state=0),不管同步队列里有没有等待了很久的线程,直接 CAS 抢锁,这是第二次插队机会。

protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // Sync基类中的公共非公平获取逻辑 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 锁处于空闲状态 if (c == 0) { // 直接CAS抢锁,不判断队列是否有等待线程 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; }

4.3 公平锁 FairSync 源码解析

公平锁的核心原则是先来先服务(FIFO):线程获取锁时,必须先检查同步队列中是否有其他线程在等待,只有队列中没有更早的等待线程时,才尝试获取锁,否则直接进入队列排队。

lock () 入口:不插队,直接走标准流程

公平锁的lock()方法没有提前抢锁的操作,直接调用 AQS 的acquire方法,严格遵守排队规则。

final void lock() { acquire(1); }

tryAcquire:公平性核心,先判断队列

公平锁的tryAcquire与非公平锁的唯一区别,就是在锁空闲时,会先调用hasQueuedPredecessors()判断队列中是否有前驱等待线程,只有没有更早的等待者时,才尝试获取锁。

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; }

公平性核心:hasQueuedPredecessors

这是实现公平锁的关键方法,用于判断同步队列中是否存在比当前线程等待更久的线程

public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; // 返回true:队列中有更早的等待线程,当前线程不能插队 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }

逻辑拆解:

  1. h != t:头节点不等于尾节点,说明队列不为空,有线程在等待
  2. (s = h.next) == null:头节点的后继节点为空,说明有线程正在执行入队操作(已经设置了 tail,但还没设置 prev 的 next 指针),此时认为队列中有等待者
  3. s.thread != Thread.currentThread():后继节点存在,但绑定的线程不是当前线程,说明有其他线程比当前线程等待更久

只要满足 “队列不为空,且第一个等待线程不是当前线程”,就返回 true,当前线程必须排队,从而保证公平性。

4.4 公平锁 vs 非公平锁 核心对比

对比维度非公平锁(默认)公平锁
插队机会两次插队:lock 时直接 CAS 抢锁;tryAcquire 时再次抢锁无任何插队机会,严格遵循 FIFO
核心判断锁空闲就直接抢,不关心等待队列锁空闲时必须先判断队列,无更早等待者才抢
吞吐量高,线程挂起唤醒的上下文切换次数少低,所有线程严格排队,上下文切换频繁
线程饥饿可能出现:新来的线程一直插队,队列中的线程长期获取不到锁不会出现,所有线程按等待顺序依次获取
适用场景绝大多数业务场景,追求高吞吐量、高性能对执行顺序有严格要求,需要避免饥饿的场景

五、Condition 条件队列:双队列联动与等待唤醒机制

Condition是显式锁体系对 “等待 - 通知” 模式的进阶实现。它解决了内置锁Object.wait()/notify()只能绑定一个等待队列、无法实现精准唤醒的缺陷,一个Lock可以绑定多个独立的Condition条件队列,分别对应不同的等待条件,典型应用如ArrayBlockingQueue的 “队列非空”“队列非满” 双条件设计。

Condition本身是接口,其核心实现是 AQS 的内部类ConditionObject,它依托 AQS 的同步队列,额外维护了一套独立的条件等待队列,节点在 “条件队列” 与 “同步队列” 之间完成状态流转,实现等待与唤醒的完整闭环。

5.1 条件队列的结构设计

核心字段与链表结构

ConditionObject内部维护了一个单向链表结构的条件等待队列,通过头尾两个指针定位队列,节点复用 AQS 的Node内部类,通过nextWaiter字段串联链表。

public class ConditionObject implements Condition, java.io.Serializable { // 条件队列头节点:第一个等待条件的节点 private transient Node firstWaiter; // 条件队列尾节点:最后一个等待条件的节点 private transient Node lastWaiter; // 中断处理模式:等待结束后抛出中断异常 private static final int THROW_IE = -1; // 中断处理模式:等待结束后重置中断标记 private static final int REINTERRUPT = 1; }

同步队列与条件队列的核心差异

条件队列中的节点,waitStatus固定为Node.CONDITION(-2),代表节点正在等待条件触发,暂时脱离锁的竞争。

维度同步队列(AQS 内置)条件队列(Condition 维护)
链表结构双向链表,prev/next 指针单向链表,nextWaiter 指针
等待目标等待获取锁(同步状态)等待某个业务条件满足
节点状态0、SIGNAL(-1)、CANCELLED(1)、PROPAGATE(-3)CONDITION(-2)
数量1 个 Lock 对应 1 个同步队列1 个 Lock 可对应 N 个条件队列
节点归属未抢到锁的线程全部在此排队主动调用 await () 的线程在此等待

一个节点同一时间只能存在于一个队列中:要么在同步队列抢锁,要么在条件队列等条件,唤醒过程本质就是节点从条件队列迁移到同步队列的过程。

5.2 await () 等待流程源码解析

调用await()的前置条件:当前线程必须已经持有对应 Lock 锁,这和Object.wait()必须在同步块中调用是同一逻辑 —— 保证队列修改与锁释放的线程安全性。

await()的完整执行逻辑可以概括为五步:

  1. 线程安全地创建节点,加入条件队列尾部
  2. 完全释放当前持有的锁(处理可重入场景)
  3. 阻塞当前线程,等待被唤醒或中断
  4. 被唤醒后,从条件队列迁移至同步队列
  5. 在同步队列中重新竞争锁,竞争成功后恢复执行

await () 入口主流程

public final void await() throws InterruptedException { // 1. 前置校验:线程已中断则直接抛异常 if (Thread.interrupted()) throw new InterruptedException(); // 2. 创建CONDITION状态节点,加入条件队列尾部 Node node = addConditionWaiter(); // 3. 完全释放锁(含重入次数),返回释放前的state值 int savedState = fullyRelease(node); int interruptMode = 0; // 4. 自旋:只要节点还没进入同步队列,就持续阻塞 while (!isOnSyncQueue(node)) { // 阻塞当前线程 LockSupport.park(this); // 检查是否因中断被唤醒,记录中断模式 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 5. 节点已进入同步队列,调用acquireQueued重新抢锁 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; // 6. 清理条件队列中已取消的节点 if (node.nextWaiter != null) unlinkCancelledWaiters(); // 7. 根据中断模式处理最终结果 if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }

核心子方法解析

  1. addConditionWaiter:加入条件队列创建状态为CONDITION的节点,追加到条件队列尾部;如果发现尾节点已取消,先执行一次队列清理。

    private Node addConditionWaiter() {
    Node t = lastWaiter;
    // 尾节点已取消,先清理所有无效节点
    if (t != null && t.waitStatus != Node.CONDITION) {
    unlinkCancelledWaiters();
    t = lastWaiter;
    }
    // 新建节点,状态为CONDITION
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 队列为空则设为头节点,否则追加到尾部
    if (t == null)
    firstWaiter = node;
    else
    t.nextWaiter = node;
    lastWaiter = node;
    return node;
    }

2.fullyRelease:完全释放锁

因为 ReentrantLock 是可重入锁,线程可能多次获取锁(state>1),调用 await 时必须一次性释放全部同步状态,否则其他线程永远无法获取锁;同时记录释放前的 state 值,后续重新获锁时恢复重入次数。

final int fullyRelease(Node node) { boolean failed = true; try { int savedState = getState(); // 一次性释放全部state if (release(savedState)) { failed = false; return savedState; } else { // 释放失败说明当前线程未持有锁,抛出非法监视器异常 throw new IllegalMonitorStateException(); } } finally { // 释放失败则标记节点为取消状态 if (failed) node.waitStatus = Node.CANCELLED; } }

5.3 signal () 唤醒流程源码解析

signal()的核心作用并不是直接唤醒线程让它运行,而是把满足条件的节点从条件队列,迁移到同步队列尾部,线程并不会立刻执行,它需要进入同步队列后,排队等待获取锁,才能真正恢复运行。

signal () 主流程

public final void signal() { // 前置校验:当前线程必须持有锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) // 唤醒条件队列中第一个有效节点 doSignal(first); }

doSignal:遍历唤醒首个有效节点

从条件队列头开始遍历,找到第一个未取消的节点,执行转移操作;如果节点已取消则继续往后找。

private void doSignal(Node first) { do { // 头节点后移,若后续无节点则尾指针置空 if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; // 转移节点到同步队列,失败则继续处理下一个 } while (!transferForSignal(first) && (first = firstWaiter) != null); }

transferForSignal:节点转移核心

这是双队列联动的核心方法,完成节点从条件队列到同步队列的迁移与状态转换:

final boolean transferForSignal(Node node) { // 1. CAS修改状态:从CONDITION改为初始0 // 修改失败说明节点已被取消,直接返回false if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; // 2. 调用enq方法,将节点加入同步队列尾部,返回前驱节点 Node p = enq(node); int ws = p.waitStatus; // 3. 检查前驱节点状态: // - 前驱已取消,或无法设置为SIGNAL,则直接唤醒当前线程 // - 让线程自己去同步队列中竞争并清理无效节点 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }

这里有一个关键设计:signal () 不一定会立刻 unpark 线程。如果前驱节点状态正常且成功设为 SIGNAL,就不唤醒线程,等前驱节点释放锁时再统一唤醒,减少不必要的上下文切换。

5.4 双队列完整状态流转

结合 AQS 同步队列,节点的全生命周期流转对应线程的等待与唤醒全过程:

  1. 持有锁阶段:线程 A 成功获取锁,成为同步队列头节点对应的执行线程,state>0。
  2. 进入条件队列:线程 A 调用await(),创建 waitStatus=CONDITION 的节点,追加到条件队列尾部;同时调用fullyRelease完全释放锁,state 归零。
  3. 阻塞等待:线程 A 执行LockSupport.park()进入阻塞状态,此时节点仅存在于条件队列,脱离同步队列。
  4. 触发唤醒:线程 B 获取锁后调用signal(),取出条件队列头节点,CAS 将 waitStatus 从 CONDITION 改为 0,调用enq将节点加入同步队列尾部。
  5. 同步队列排队:节点进入同步队列后,遵循独占锁的排队规则:前驱节点设为 SIGNAL,线程继续阻塞等待。
  6. 重新获锁:前驱节点释放锁时唤醒该节点,线程竞争锁成功,重新设置 state 为之前的重入次数,从 await 方法返回,继续执行业务代码
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/27 1:00:05

浅谈WEB页面提速(前端向)

浅谈WEB页面提速&#xff08;前端向&#xff09; 大家好&#xff0c;我是你们的老朋友&#xff0c;一个喜欢在代码世界里“抢时间”的技术博主。今天我们来聊聊一个让所有前端开发者又爱又恨的话题——WEB页面提速。在这个“5秒不加载&#xff0c;用户就开溜”的时代&#xff…

作者头像 李华
网站建设 2026/7/27 0:59:09

问题驱动学习的SOP

问题驱动学习&#xff0c;不是遇到问题才临时抱佛脚&#xff0c;而是建立一套“发现问题 → 拆解问题 → 学习知识 → 实践验证 → 沉淀能力”的成长闭环。很多人的学习&#xff1a; 学习知识↓ 收藏资料↓ 感觉懂了↓ 不知道怎么用问题驱动&#xff1a; 现实问题↓ 能力缺口↓…

作者头像 李华
网站建设 2026/7/27 0:55:52

降维算法75倍加速:从PCA到稀疏字典学习的工程实践

# 降维算法75倍加速&#xff1a;从PCA到稀疏字典学习的工程实践## 背景&#xff1a;高维数据下的性能瓶颈在机器学习工程中&#xff0c;特征维度爆炸是常见痛点。无论是图像处理&#xff08;如CNN之前的特征提取&#xff09;、NLP中的词嵌入降维&#xff0c;还是推荐系统中的用…

作者头像 李华
网站建设 2026/7/27 0:50:57

终极指南:3分钟免费实现GitHub界面全面汉化

终极指南&#xff1a;3分钟免费实现GitHub界面全面汉化 【免费下载链接】github-chinese GitHub 汉化插件&#xff0c;GitHub 中文化界面。 (GitHub Translation To Chinese) 项目地址: https://gitcode.com/gh_mirrors/gi/github-chinese 还在为GitHub的英文界面而头疼…

作者头像 李华
网站建设 2026/7/27 0:32:21

一款基于 .NET 开源美观、功能丰富的串口调试工具

一款基于 .NET 开源美观、功能丰富的串口调试工具 作为嵌入式开发者和物联网工程师&#xff0c;串口调试工具是我们日常工作中不可或缺的利器。从简单的数据收发&#xff0c;到复杂的协议解析、自动应答、波形显示&#xff0c;一个功能强大的串口调试工具能让我们的开发效率倍增…

作者头像 李华
网站建设 2026/7/27 0:28:08

5分钟快速上手:Windows本地实时语音转文字工具TMSpeech终极指南

5分钟快速上手&#xff1a;Windows本地实时语音转文字工具TMSpeech终极指南 【免费下载链接】TMSpeech 腾讯会议摸鱼工具 项目地址: https://gitcode.com/gh_mirrors/tm/TMSpeech 你是否经常需要将会议录音、在线课程或视频内容转换为文字&#xff1f;是否担心隐私泄露&…

作者头像 李华