Yang's blog Yang's blog
首页
Java
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

xiaoyang

编程爱好者
首页
Java
密码学
机器学习
命令手册
关于
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • SpringCloud

    • 微服务架构介绍
    • SpringCloud介绍
    • Spring Cloud:生产者与消费者
    • Spring Cloud Eureka:构建可靠的服务注册与发现
    • Spring Cloud Ribbon:负载均衡
    • Spring Cloud Fegin:服务调用
    • Spring Cloud Hystrix:熔断器
    • Spring Cloud Zuul:统一网关路由
    • Spring Cloud Config:配置中心
  • Java后端框架

    • LangChain4j

      • 介绍
      • 快速开始
      • Chat and Language Models
      • Chat Memory
      • Model Parameters
      • Response Streaming
      • AI Services
      • Agent
      • Tools (Function Calling)
      • RAG
      • Structured Outputs
      • Classification
      • Embedding (Vector) Stores
      • Image Models
      • Quarkus Integration
      • Spring Boot Integration
      • Kotlin Support
      • Logging
      • Observability
      • Testing and Evaluation
      • Model Context Protocol
  • 八股文

    • 操作系统
    • JVM介绍
    • Java多线程
    • Java集合框架
    • Java反射
    • JavaIO
    • Mybatis介绍
    • Spring介绍
    • SpringBoot介绍
    • Mysql
    • Redis
    • 数据结构
    • 云计算
    • 设计模式
    • 计算机网络
    • 锁核心类AQS
      • 1. AQS概述
        • 1.1 AQS是什么?
        • 1.2 AQS的设计目标
        • 1.3 AQS的应用场景
      • 2. AQS的核心数据结构
        • 2.1 AQS加锁流程
        • 2.2 exclusiveOwnerThread变量
        • 2.2 State变量(同步状态)
        • 2.3 Node 节点结构
        • 2.4 条件队列(Condition Queue)
        • 2.5 FIFO等待队列(CLH队列)
      • 3. AQS的模版方法模式
        • 3.1 独占模式和共享模式
        • 3.2 独占模式方法
        • 3.3 共享模式方法
        • 3.4 AQS中的抽象方法
        • 3.5 模板方法模式
      • 3. AQS的工作原理
        • 3.1 获取锁(acquire)
        • 3.2 释放锁(release)
        • 3.3 共享模式和独占模式
        • 共享模式获取锁:
        • 共享模式释放锁:
        • 3.4 可中断与超时支持
        • 可中断获取锁:
        • 超时获取锁:
      • 4. 经典问题与面试高频考点
        • 4.1 AQS的底层实现原理
        • 4.2 公平锁与非公平锁的区别
        • 4.3 自旋锁与阻塞锁的对比
        • 4.4 独占模式 vs 共享模式的应用场景
        • 4.5 如何自定义一个同步器?
    • Nginx
  • 前端技术

    • 初识Vue3
    • Vue3数据双向绑定
    • Vue3生命周期
    • Vue-Router 组件
    • Pinia 集中式状态存储
  • 中间件

    • RocketMQ
  • 开发知识

    • 请求参数注解
    • 时间复杂度和空间复杂度
    • JSON序列化与反序列化
    • Timestamp vs Datetime
    • Java开发中必备能力单元测试
    • 正向代理和反向代理
    • 什么是VPN
    • 正则表达式
  • Java
  • 八股文
xiaoyang
2025-03-11
目录

锁核心类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 的设计目标主要包括:

  1. 简化同步器的开发
    • 线程同步涉及队列管理、阻塞/唤醒、CAS操作等,AQS 封装了底层逻辑,只需要实现核心方法(tryAcquire、tryRelease等)即可自定义同步器。
  2. 支持两种同步模式
    • 独占模式(Exclusive):一次只能被一个线程获取,如 ReentrantLock。
    • 共享模式(Shared):多个线程可同时获取,如 Semaphore、CountDownLatch。
  3. 基于CLH(FIFO)队列 实现同步等待队列
    • 线程获取资源失败后会被添加到AQS的队列,按照**先进先出(FIFO)**的方式调度。
  4. 高效的线程阻塞和唤醒机制
    • AQS 通过 LockSupport.park/unpark 高效管理线程阻塞和唤醒,减少CPU空转,提高性能。
  5. 可扩展性 & 可复用性
    • AQS 不直接实现锁逻辑,而是提供了 通用的同步框架,具体的同步逻辑由子类(如 ReentrantLock)来实现。

# 1.3 AQS的应用场景

AQS 作为 JUC 并发工具的核心,几乎所有的同步类都基于 AQS 实现,包括:

工具类 模式 作用
ReentrantLock 独占模式 可重入锁,支持公平/非公平模式
CountDownLatch 共享模式 计数器,等待多个线程执行完毕
Semaphore 共享模式 计数信号量,控制并发线程数
ReadWriteLock 读-共享,写-独占 读写分离,提高并发性能
CyclicBarrier 计数同步 让一组线程达到某个点后再继续执行
Phaser 多阶段栅栏 更灵活的CyclicBarrier

# 2. AQS的核心数据结构

# 2.1 AQS加锁流程

AQS 的加锁流程主要围绕 同步队列(CLH 队列) 和 条件队列(Condition Queue) 进行,核心机制包括:

  1. 多个线程竞争锁,只有一个线程成功,其他线程进入 同步队列 排队。
  2. 持有锁的线程可以释放锁,进入 条件队列 等待某个条件成立时被唤醒。
  3. 线程唤醒后,重新进入 同步队列,按照 FIFO 规则尝试获取锁。

image-20250311142710059

# 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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2.2 State变量(同步状态)

private volatile int state;
1

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;
    }
}
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
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;
 }
1
2
3
4
5
6

image-20250311145421662

# 2.5 FIFO等待队列(CLH队列)

概念: 在 AQS 中,FIFO 等待队列是一种 先进先出(FIFO) 的队列结构,通常用于管理等待获取锁的线程。AQS 使用了一种特殊的队列结构——CLH(Craig, Landin, and Hagersten)队列,来确保线程按顺序获取锁。

CLH队列是一个双向链表结构,用于保证多个线程按顺序获取锁。每个线程都会有一个对应的 Node 节点,且队列的头部表示当前持有锁的线程,队尾表示等待锁的线程。

image-20250311145717188

# 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();
}
1
2
3
4
5

执行流程:

  1. 首先调用tryAcquire尝试获取锁,这个方法需要子类实现
  2. 如果获取失败,创建一个独占模式的节点并加入等待队列
  3. 调用acquireQueued使线程在队列中等待锁
  4. 如果等待过程中被中断,最后调用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;
}
1
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);
    }
}
1
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;
}
1
2
3
4
5
6
7
8
9

执行流程:

  1. 调用tryRelease尝试释放锁,这个方法需要子类实现
  2. 如果释放成功,检查头节点的等待状态
  3. 如果需要,唤醒后继节点的线程

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);
}
1
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);
}
1
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);
    }
}
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
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();
    }
}
1
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;
}
1
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;
    }
}
1
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);
}
1
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);
    }
}
1
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);
}
1
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);
    }
}
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
27
28
29
30
31
32
33

这就是AQS的核心工作原理,它通过这些机制提供了一套灵活的同步框架,各种同步器如ReentrantLock、Semaphore、CountDownLatch等都是基于这个框架实现的。

# 4. 经典问题与面试高频考点

# 4.1 AQS的底层实现原理

AQS (AbstractQueuedSynchronizer) 的底层实现原理可以总结为以下几点:

  1. 状态变量:AQS维护一个volatile整型变量state作为同步状态,通过CAS操作和volatile语义保证其可见性和原子性。

  2. FIFO队列:使用一个基于双向链表实现的CLH队列来管理等待线程。当线程获取锁失败时,会被封装成Node加入队列尾部。

  3. Node结构:队列节点包含线程引用、等待状态(waitStatus)、前驱和后继指针。waitStatus有多种值:

    • CANCELLED (1):线程取消
    • SIGNAL (-1):后继节点需要被唤醒
    • CONDITION (-2):节点在条件队列中
    • PROPAGATE (-3):共享锁需要向后传播
    • 0:初始状态
  4. 模板方法模式:AQS定义了框架结构,但具体的锁获取与释放逻辑由子类实现:

    protected boolean tryAcquire(int arg) { 
        throw new UnsupportedOperationException(); 
    }
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    
    1
    2
    3
    4
    5
    6
  5. 线程阻塞与唤醒:使用LockSupport.park()和LockSupport.unpark()实现线程的阻塞与唤醒,而不是synchronized的monitor机制。

  6. CAS操作:使用Unsafe类提供的CAS方法进行非阻塞的原子操作,如修改state、队列节点状态等。

# 4.2 公平锁与非公平锁的区别

公平锁和非公平锁的主要区别在于获取锁的顺序策略:

  1. 定义区别:

    • 公平锁:严格按照线程在队列中的FIFO顺序获取锁
    • 非公平锁:允许新到达的线程在入队前尝试获取锁,可能"插队"
  2. 实现区别(以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
  3. 性能对比:

    • 非公平锁:吞吐量更高,但可能导致某些线程长时间等待(饥饿)
    • 公平锁:等待时间更可预测,但有额外开销(需要检查队列)
  4. 适用场景:

    • 非公平锁:高并发、追求吞吐量的场景
    • 公平锁:对线程等待公平性有要求的场景

# 4.3 自旋锁与阻塞锁的对比

自旋锁和阻塞锁代表了两种不同的线程等待策略:

  1. 工作原理:

    • 自旋锁:线程通过循环检查锁状态,不放弃CPU时间片
    • 阻塞锁:线程被挂起(park),放弃CPU时间片
  2. 适用场景:

    • 自旋锁:适合锁竞争不激烈、锁持有时间短的场景
    • 阻塞锁:适合锁竞争激烈、锁持有时间长的场景
  3. 优缺点对比:

    特性 自旋锁 阻塞锁
    CPU消耗 高(一直占用CPU) 低(放弃CPU时间片)
    线程切换 无上下文切换 有上下文切换开销
    响应速度 快(无需唤醒) 慢(需要唤醒过程)
    适用锁持有时间 短 长
    系统负载影响 高负载下性能下降明显 高负载下相对稳定
  4. AQS中的应用:AQS结合了两者优势,使用"适应性自旋"策略:

    • 先进行一定次数自旋尝试
    • 自旋失败后再进入阻塞状态
    • 自旋次数会根据历史成功率动态调整

# 4.4 独占模式 vs 共享模式的应用场景

AQS支持两种同步模式,它们适用于不同的场景:

  1. 独占模式(Exclusive):
    • 定义:同一时刻只允许一个线程访问资源
    • 实现方法:重写tryAcquire和tryRelease
    • 典型应用:
      • ReentrantLock:独占可重入锁
      • WriteLock:读写锁中的写锁
      • ThreadPoolExecutor的Worker锁
    • 应用场景:需要互斥访问的场景,如数据修改操作
  2. 共享模式(Shared):
    • 定义:同一时刻允许多个线程同时访问资源
    • 实现方法:重写tryAcquireShared和tryReleaseShared
    • 典型应用:
      • Semaphore:信号量,控制并发线程数
      • CountDownLatch:倒计时门闩,等待多个操作完成
      • ReadLock:读写锁中的读锁
      • CyclicBarrier:循环栅栏(内部使用ReentrantLock+Condition)
    • 应用场景:允许多个线程并发访问的场景,如读操作、资源池限流
  3. 选择依据:
    • 资源是否允许并发访问
    • 是否需要控制并发线程数量
    • 操作是读密集型还是写密集型

# 4.5 如何自定义一个同步器?

自定义同步器需要继承AQS并实现相应的方法:

  1. 选择模式:确定是独占模式还是共享模式(或两者都支持)
  2. 实现基本方法:
    • 独占模式:实现tryAcquire和tryRelease
    • 共享模式:实现tryAcquireShared和tryReleaseShared
    • 可根据需要同时实现两种模式
  3. 定义状态含义:明确AQS中state变量的含义,如:
    • 锁计数(如ReentrantLock)
    • 可用许可证数量(如Semaphore)
    • 等待线程数(如CountDownLatch)
  4. 设计API:包装AQS方法,提供友好的接口
  5. 示例:简单的二元信号量实现:
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();
    }
}
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
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,可以轻松获得线程安全、可靠的队列管理和线程调度能力,而不必关心底层的实现细节。

编辑 (opens new window)
上次更新: 2025/04/01, 01:48:12

← 计算机网络 Nginx→

最近更新
01
操作系统
03-18
02
Nginx
03-17
03
后端服务端主动推送消息的常见方式
03-11
更多文章>
Theme by Vdoing | Copyright © 2023-2025 xiaoyang | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式