深入解析AQS:设计模式、ReentrantLock实现与自定义锁开发

一、模板方法模式:AQS的架构基石

1.1 模式核心思想

模板方法模式通过固定算法骨架+可变实现细节的设计,实现了代码复用与扩展性的平衡。AQS采用这种模式,将同步器的核心流程(如线程排队、阻塞唤醒)固化在父类,仅将资源获取/释放的逻辑通过抽象方法交给子类实现。

设计优势:

  • 保证正确性:关键同步流程不可修改
  • 提高复用:通用逻辑只需实现一次
  • 便于扩展:子类只需关注业务逻辑

1.2 完整代码示例

// 模板类定义
abstract class BeverageMaker {
// 模板方法(final防止重写)
public final void makeBeverage() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
} // 具体方法(通用步骤)
private void boilWater() {
System.out.println("煮沸水");
} private void pourInCup() {
System.out.println("倒入杯中");
} // 抽象方法(子类必须实现)
protected abstract void brew();
protected abstract void addCondiments(); // 钩子方法(子类可选覆盖)
protected boolean customerWantsCondiments() {
return true;
}
} // 具体实现类
class TeaMaker extends BeverageMaker {
@Override
protected void brew() {
System.out.println("浸泡茶叶5分钟");
} @Override
protected void addCondiments() {
System.out.println("加入柠檬片");
} @Override
protected boolean customerWantsCondiments() {
return false; // 茶不需要调料
}
}

关键点说明:

  1. makeBeverage()定义了不可变的制作流程
  2. brew()addCondiments()是可变部分,由子类实现
  3. customerWantsCondiments()是钩子方法,提供策略扩展点

1.3 AQS中的模板模式实现

AQS将同步操作抽象为模板方法:

public abstract class AbstractQueuedSynchronizer {
// 获取资源的模板方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取(子类实现)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} // 由子类实现的获取逻辑
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
} // 已实现的通用方法
private Node addWaiter(Node mode) {
// 实现节点入队逻辑...
} final boolean acquireQueued(Node node, int arg) {
// 实现队列中获取资源的逻辑...
}
}

设计精妙之处:

  • acquire()封装了完整的获取资源流程
  • 子类只需关注tryAcquire的核心逻辑
  • 线程排队、阻塞唤醒等复杂操作由AQS统一处理

二、ReentrantLock与AQS的深度整合

2.1 整体架构设计

ReentrantLock通过内部类继承AQS,实现锁的核心功能:

classDiagram
class ReentrantLock {
-Sync sync
+lock()
+unlock()
}

class Sync {
<<abstract>>
+nonfairTryAcquire()
+tryRelease()
}

class NonfairSync {
+lock()
+tryAcquire()
}

class FairSync {
+tryAcquire()
}

ReentrantLock --> Sync
Sync <|-- NonfairSync
Sync <|-- FairSync
Sync --> AQS

2.2 关键源码解析

非公平锁实现:

static final class NonfairSync extends Sync {
// 非公平获取锁
final void lock() {
if (compareAndSetState(0, 1)) // 先尝试快速获取
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 进入AQS排队流程
} 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) { // 锁未被持有
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;
}

实现特点:

  1. 快速路径:先直接尝试CAS获取锁,避免排队开销
  2. 重入支持:通过检查当前线程和状态计数实现
  3. 非公平性:新请求线程可能插队获取锁

公平锁实现差异:

static final class FairSync extends Sync {
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;
}
}
// 重入逻辑与非公平锁相同...
}
}

公平性保证:

  • hasQueuedPredecessors()检查是否有更早等待的线程
  • 严格按照CLH队列顺序获取锁
  • 吞吐量通常低于非公平锁,但避免线程饥饿

2.3 状态管理机制

AQS使用volatile int state字段记录同步状态,ReentrantLock中表示:

  • 0:锁未被任何线程持有
  • >0:锁被持有,数值表示重入次数

配套原子操作方法:

protected final int getState() {
return state;
} protected final void setState(int newState) {
state = newState;
} protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

三、基于AQS实现自定义锁

3.1 完整自定义锁实现

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock; /**
* 基于AQS的简单互斥锁(不可重入)
*/
public class SimpleMutexLock implements Lock {
private final Sync sync = new Sync(); // 内部同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 尝试获取锁
@Override
protected boolean tryAcquire(int acquires) {
if (acquires != 1) throw new IllegalArgumentException();
if (compareAndSetState(0, 1)) { // CAS保证原子性
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
} // 尝试释放锁
@Override
protected boolean tryRelease(int releases) {
if (releases != 1) throw new IllegalArgumentException();
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0); // 不需要CAS,只有持有线程能调用
return true;
} // 是否被当前线程独占
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
} // 创建条件变量
Condition newCondition() {
return new ConditionObject();
}
} // ========== Lock接口实现 ==========
@Override
public void lock() {
sync.acquire(1);
} @Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
} @Override
public boolean tryLock() {
return sync.tryAcquire(1);
} @Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
} @Override
public void unlock() {
sync.release(1);
} @Override
public Condition newCondition() {
return sync.newCondition();
} // ========== 扩展方法 ==========
public boolean isLocked() {
return sync.isHeldExclusively();
} public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
}

实现要点:

  1. 状态定义:0表示未锁定,1表示锁定
  2. 不可重入:不检查当前线程是否已持有锁
  3. 条件变量:直接复用AQS的ConditionObject
  4. 线程安全:CAS操作保证tryAcquire的原子性

3.2 可重入锁改造

修改Sync内部类即可实现可重入:

private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(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;
} @Override
protected boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 完全释放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}

关键修改点:

  1. 增加当前线程检查实现重入
  2. 通过状态计数记录重入次数
  3. 只有计数归零时才完全释放锁

3.3 完整测试用例

public class SimpleMutexLockTest {
public static void main(String[] args) throws InterruptedException {
// 基础功能测试
testBasicOperation(); // 并发安全测试
testConcurrentAccess(); // 不可重入性测试
testNonReentrant();
} private static void testBasicOperation() {
SimpleMutexLock lock = new SimpleMutexLock();
assert !lock.isLocked() : "初始状态应该未锁定"; lock.lock();
try {
assert lock.isLocked() : "锁定后状态应该为true";
assert !lock.tryLock() : "不可重入锁应获取失败";
} finally {
lock.unlock();
}
} private static void testConcurrentAccess() throws InterruptedException {
final int THREADS = 5;
final SimpleMutexLock lock = new SimpleMutexLock();
final AtomicInteger counter = new AtomicInteger();
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch endLatch = new CountDownLatch(THREADS); ExecutorService executor = Executors.newFixedThreadPool(THREADS);
for (int i = 0; i < THREADS; i++) {
executor.execute(() -> {
try {
startLatch.await();
lock.lock();
try {
counter.incrementAndGet();
Thread.sleep(100); // 模拟操作
} finally {
lock.unlock();
endLatch.countDown();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
} startLatch.countDown();
endLatch.await();
assert counter.get() == THREADS : "所有线程应该都执行了";
executor.shutdown();
} private static void testNonReentrant() {
SimpleMutexLock lock = new SimpleMutexLock();
lock.lock();
try {
// 会阻塞在这里,因为是不可重入锁
boolean acquired = lock.tryLock();
assert !acquired : "不可重入锁不应再次获取成功";
} finally {
lock.unlock();
}
}
}

四、核心知识总结

4.1 模板方法模式在AQS中的应用

  1. 固定流程

    • acquire()/release()定义了标准的获取/释放资源流程
    • 包含线程排队、阻塞唤醒等通用逻辑
  2. 可变部分

    • tryAcquire()/tryRelease()由子类实现
    • 决定如何获取和释放资源
  3. 设计优势

    • 保证同步行为的正确性
    • 提高代码复用率
    • 支持多种同步策略

4.2 ReentrantLock实现要点

特性 非公平锁 公平锁
获取顺序 允许插队 严格FIFO
吞吐量 较低
实现关键 直接尝试CAS 先检查队列
适用场景 大多数情况 避免饥饿

4.3 自定义锁开发原则

  1. 明确状态语义:定义state字段的含义
  2. 保证线程安全:CAS操作保护关键状态
  3. 合理选择特性:根据需求决定是否支持重入、公平性等
  4. 充分测试:验证并发场景下的正确性

结语

通过本文的系统讲解,我们完成了从设计模式理论到AQS框架分析,再到具体锁实现的完整学习路径。理解这些内容后,开发者可以:

  1. 更深入地理解Java并发包的设计思想
  2. 根据特殊需求实现自定义同步器
  3. 更好地选择和使用Java提供的并发工具

建议读者结合文中的代码示例进行实践,通过修改和调试加深对AQS工作机制的理解。对于生产环境,除非有特殊需求,否则应优先使用Java标准库提供的并发工具。

【深入解析AQS】从设计模式到ReentrantLock实现再到自定义锁的更多相关文章

  1. 从ReentrantLock角度解析AQS

    是它,是它,就是它,并发包的基石: 一.概述 闲来不卷,随便聊一点. 一般情况下,大家系统中至少也是JDK8了,那想必对于JDK5加入的一系列功能并不陌生吧.那时候重点加入了java.util.con ...

  2. 图解AQS原理之ReentrantLock详解-非公平锁

    概述 并发编程中,ReentrantLock的使用是比较多的,包括之前讲的LinkedBlockingQueue和ArrayBlockQueue的内部都是使用的ReentrantLock,谈到它又不能 ...

  3. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

  4. 高并发第十一弹:J.U.C -AQS(AbstractQueuedSynchronizer) 组件:Lock,ReentrantLock,ReentrantReadWriteLock,StampedLock

    既然说到J.U.C 的AQS(AbstractQueuedSynchronizer)   不说 Lock 是不可能的.不过实话来说,一般 JKD8 以后我一般都不用Lock了.毕竟sychronize ...

  5. AQS同步组件及ReentrantLock和synchronized的区别

    AQS同步组件 CountDownLatch(只有一个线程对他进行操作): 主线程必须在启动其它线程后立即调用await()方法.这样主线程的操作就会在这个方法上阻塞,直到其它线程完成各自的任务. S ...

  6. 模仿ReentrantLock类自定义锁

    简介 临近过年了,没什么需求,今天模仿ReentrantLock自定义写了一个自己锁,在这里记录一下,前提是对AQS原理有所了解,分享给大家 1.自定义锁MyLock package com.jack ...

  7. ReentrantLock 如何实现非公平锁?和公平锁实现有什么区别

    reentrant 英[riːˈɛntrənt] 美[ˌriˈɛntrənt] 先学会读.单词原意是可重入的 考察显示锁的使用.可延伸知识点 独占锁 & 共享锁 独占锁 - 悲观锁(不能同时被 ...

  8. Json解析工具Jackson(使用注解)--jackson框架自定义的一些json解析注解

    Json解析工具Jackson(使用注解)--jackson框架自定义的一些json解析注解 @JsonIgnoreProperties 此注解是类注解,作用是json序列化时将Javabean中的一 ...

  9. 多线程与高并发(三)—— 源码解析 AQS 原理

    一.前言 AQS 是一个同步框架,关于同步在操作系统(一)-- 进程同步 中对进程同步做了些概念性的介绍,我们了解到进程(线程同理,本文基于 JVM 讲解,故下文只称线程)同步的工具有很多:Mutex ...

  10. 并发编程(三):从AQS到CountDownLatch与ReentrantLock

    一.目录      1.AQS简要分析      2.谈CountDownLatch      3.谈ReentrantLock      4.谈消费者与生产者模式(notfiyAll/wait.si ...

随机推荐

  1. 【VMware vSphere】扩容或缩减 vCenter Server 的磁盘空间大小。

    我们在部署 vCenter Server 时,根据不同环境的情况,可以选择不同的部署选项,比如环境中的主机可能运行了 100 个,或者虚拟机运行了 1000 个,此时按照官方推荐的选择"小型 ...

  2. WinForm 多线程+委托来防止界面假死

    参考: http://www.cnblogs.com/xpvincent/archive/2013/08/19/3268001.html 当有大量数据需要计算.显示在界面或者调用sleep函数时,容易 ...

  3. 你还不会使用curl发送请求吗?一篇博客搞定!

    前言:以下均为Windows使用,使用前不需要任何准备,打开命令提示符根据指令即可使用关键字: curl 注意: 建议在请求前ping一下 ping http://www.123.com 或 ping ...

  4. 使用mongodb、Kafka保存mqtt消息

    一.引言 随着物联网技术的迅猛发展,大量的设备和传感器产生了海量的数据.本文利用了 MQTT.Kafka 和 MongoDB 各自的优点,满足实时数据处理和大规模数据存储的需求. 如图: 二.总结 优 ...

  5. Deepseek深度求索教程:从入门到精通,免费获取清华大学新闻学院104页完整指南

    在当今信息爆炸的时代,如何高效地获取和利用知识成为了每个人面临的挑战.Deepseek深度求索作为一款强大的信息检索工具,正逐渐成为学术界和专业人士的首选.为了帮助大家更好地掌握Deepseek的使用 ...

  6. Vulnhub-Hackme

    一.靶机搭建 选择扫描虚拟机 选择路径即可 二.信息收集 靶机信息 Name: hackme: 1 Date release: 18 Jul 2019 难度:初级,目标是通过web漏洞获得有限的权限访 ...

  7. LayerSkip: 使用自推测解码加速大模型推理

    自推测解码是一种新颖的文本生成方法,它结合了推测解码 (Speculative Decoding) 的优势和大语言模型 (LLM) 的提前退出 (Early Exit) 机制.该方法出自论文 Laye ...

  8. uniapp vue3 setup + 云开发开发个人小程序

    最近使用uniapp vue3 setup + 云开发开发了个人小程序,设计使用figma软件,看下成品截图吧(可以直接微信搜索[识光]小程序体验,或者最底部有码可以直接扫)

  9. 魔方求解器程序(层先法,java版本)

    实现了一个三阶魔方的层先法求解程序:https://github.com/davelet/java-puzzle-resolver 欢迎试用. 用法 1. 随机试用 不关注起始状态的话可以用程序的随机 ...

  10. IvorySQL 增量备份与合并增量备份功能解析

    1. 概述 IvorySQL v4 引入了块级增量备份和增量备份合并功能,旨在优化数据库备份与恢复流程.通过 pg_basebackup 工具支持增量备份,显著降低了存储需求和备份时间.同时,pg_c ...