AQS在锁实现中的应用详解

AQS在锁实现中的应用详解

一、ReentrantLock 实现原理
1. 核心设计
  • 独占锁:基于AQS独占模式实现
  • 可重入:支持同一线程多次获取锁
  • 公平/非公平模式 :通过内部类FairSync和NonfairSync实现
2. AQS状态映射
  • state含义:锁的重入计数
    • 0:锁未被占用
    • >0:锁被占用的次数(重入次数)
3. 核心实现代码

非公平锁获取(NonfairSync.tryAcquire):

java 复制代码
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 1. 锁未被占用,直接CAS获取
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 2. 锁已被当前线程占用,重入计数+1
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // 溢出检查
            throw new Error("Maximum lock count exceeded");
        setState(nextc); // 已持有锁,无需CAS
        return true;
    }
    return false; // 锁被其他线程占用
}

公平锁获取(FairSync.tryAcquire):

java 复制代码
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 公平性体现:检查CLH队列是否有等待的线程
        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;
}

锁释放(Sync.tryRelease):

java 复制代码
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { // 重入计数为0,释放锁
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
二、CountDownLatch 实现原理
1. 核心设计
  • 共享锁:基于AQS共享模式实现
  • 一次性:计数器减为0后不可重置
  • 多线程等待:多个线程可等待同一个计数器
2. AQS状态映射
  • state含义:计数器值
    • 初始化为构造函数传入的计数
    • 调用countDown()时,state递减
    • 当state=0时,所有等待线程被唤醒
3. 核心实现代码

共享锁获取(tryAcquireShared):

java 复制代码
protected int tryAcquireShared(int acquires) {
    // 计数器为0时,获取锁成功(返回1);否则失败(返回-1)
    return (getState() == 0) ? 1 : -1;
}

共享锁释放(tryReleaseShared):

java 复制代码
protected boolean tryReleaseShared(int releases) {
    // 循环CAS递减计数器,直到为0
    for (;;) {
        int c = getState();
        if (c == 0)
            return false; // 计数器已为0,无需处理
        int nextc = c - 1;
        if (compareAndSetState(c, nextc))
            return nextc == 0; // 计数器减为0时,返回true,唤醒所有等待线程
    }
}
三、Semaphore 实现原理
1. 核心设计
  • 共享锁:基于AQS共享模式实现
  • 许可机制 :通过state控制可用资源数
  • 公平/非公平模式:与ReentrantLock类似
2. AQS状态映射
  • state含义:可用许可数
    • 初始化为构造函数传入的许可数
    • 调用acquire()时,state递减(获取许可)
    • 调用release()时,state递增(释放许可)
3. 核心实现代码

非公平获取许可(NonfairSync.tryAcquireShared):

java 复制代码
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        // 许可不足或CAS失败时,返回负数(获取失败)
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

公平获取许可(FairSync.tryAcquireShared):

java 复制代码
protected int tryAcquireShared(int acquires) {
    for (;;) {
        // 公平性体现:检查CLH队列是否有等待的线程
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

释放许可(Sync.tryReleaseShared):

java 复制代码
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // 溢出检查
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true; // 释放成功,唤醒后续等待线程
    }
}
四、CountDownLatch 与 CyclicBarrier 的区别(面试重点)
对比维度 CountDownLatch CyclicBarrier
底层实现 基于AQS共享模式 基于ReentrantLock + Condition
是否可重置 不可重置(一次性) 可重置(通过reset()方法)
状态管理 递减计数器(state从N→0) 递增等待线程数(直到达到parties)
使用场景 一个/多个线程等待其他线程完成 多个线程相互等待,达到屏障后继续执行
等待线程数 等待的线程数不固定(可多可少) 固定数量的线程必须全部到达屏障
唤醒机制 计数器为0时,自动唤醒所有等待线程 最后一个线程到达时,唤醒所有等待线程
异常处理 无特殊异常处理 支持BrokenBarrierException,表示屏障被破坏
扩展功能 不支持回调 支持barrierAction,最后一个线程到达时执行

典型使用场景示例:

  • CountDownLatch :主线程等待多个子线程完成初始化后再执行

    java 复制代码
    CountDownLatch latch = new CountDownLatch(3);
    // 3个子线程执行完成后调用countDown()
    // 主线程调用latch.await()等待
  • CyclicBarrier :多个线程执行到某一阶段后,相互等待,然后同时继续执行

    java 复制代码
    CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程到达屏障!"));
    // 每个线程执行到barrier.await()时等待
    // 3个线程都到达后,执行barrierAction,然后继续执行
五、AQS同步器对比总结
同步器 模式 state含义 核心特性 典型场景
ReentrantLock 独占 重入计数 可重入、公平/非公平 替代synchronized,支持更灵活的锁控制
CountDownLatch 共享 计数器值 一次性、多线程等待 主线程等待子线程初始化完成
Semaphore 共享 可用许可数 可多次获取/释放、公平/非公平 限流、资源池控制

面试题:CountDownLatch与CyclicBarrier的区别?

回答模板 :

CountDownLatch和CyclicBarrier都是Java并发包中用于线程同步的工具类,但它们在底层实现、功能特性和使用场景上有明显区别:

  1. 底层实现不同:

    • CountDownLatch基于AQS共享模式实现,通过state变量维护计数器
    • CyclicBarrier基于ReentrantLock和Condition实现,内部维护等待线程数
  2. 是否可重置:

    • CountDownLatch是一次性的,计数器减为0后不可重置
    • CyclicBarrier是可重置 的,通过reset()方法可以重新使用
  3. 同步机制:

    • CountDownLatch:一个/多个线程等待其他线程完成,计数器从N→0
    • CyclicBarrier:多个线程相互等待,直到所有线程到达屏障点
  4. 等待线程数:

    • CountDownLatch:等待的线程数不固定,可多可少
    • CyclicBarrier:必须有固定数量的线程到达屏障
  5. 扩展功能:

    • CountDownLatch:无回调功能
    • CyclicBarrier:支持barrierAction,最后一个线程到达时执行回调
  6. 使用场景:

    • CountDownLatch:适合主从协作场景,如主线程等待子线程完成初始化
    • CyclicBarrier:适合多线程协作场景,如多个线程分阶段执行任务,每个阶段等待所有线程完成

示例代码对比:

java 复制代码
// CountDownLatch示例:主线程等待3个子线程完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            // 模拟任务执行
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown(); // 子线程完成,计数器减1
        }
    }).start();
}
latch.await(); // 主线程等待计数器为0

// CyclicBarrier示例:3个线程相互等待,到达屏障后继续执行
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程到达屏障!"));
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            // 第一阶段任务
            Thread.sleep(1000);
            barrier.await(); // 等待其他线程到达屏障
            // 第二阶段任务
            Thread.sleep(1000);
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }).start();
}

总结

AQS作为Java并发包的核心框架,通过模板方法模式为各种同步器提供了统一的实现基础。ReentrantLock、CountDownLatch、Semaphore等同步器通过重写AQS的tryAcquire/tryRelease等方法,实现了各自独特的同步语义。

CountDownLatch和CyclicBarrier作为常用的线程同步工具,虽然都用于线程等待,但在实现原理、功能特性和使用场景上有本质区别。理解它们的区别,对于正确选择和使用同步工具至关重要,也是面试中的高频考点。

相关推荐
驭渊的小故事2 小时前
MyBatis XML 开发实战:从配置到 CRUD 全解析
xml·java·mybatis
哦虎!7 小时前
【JavaEE】网络初识
java
for_ever_love__8 小时前
MySQL ORDER BY 排序优化:从 Using filesort 到索引有序扫描(含双路 / 单路排序原理)
java·mysql·优化·存储·by·order·filesort
我是人✓8 小时前
Java文件上传三方式对比:从传统到SpringBoot
java·开发语言
欣栀♡9 小时前
JVM 频繁 Full GC 先看日志括号里的 cause,jstat 的 FGC 在 JDK 8 上会骗人
java·jvm
IT研究室9 小时前
最新计算机毕业设计选题推荐-基于spring boot的葡萄酒文化宣传网站-网站-文档指导-Java-springboot
java·spring boot·课程设计
慧都小项9 小时前
MyEclipse 中创建和测试 JAX-RS REST 接口:从项目配置到 REST Explorer
java·restful·myeclipse·jax-rs
Wang's Blog9 小时前
Java 项目实战: 交友项目 - 入门案例:编写 Dubbo 服务提供者
java·dubbo·交友
秋饼10 小时前
Spring AI 2.0 结构化输出企业级实战:从 BeanOutputConverter 源码到生产级解析容错
java·ai·技术分享·后端开发
摇滚侠10 小时前
《计算机是如何工作的:人人都能懂的计算机软硬件工作原理》第 9 章 机器码与汇编语言 阅读笔记 9
java·开发语言·笔记