谈谈你对 AQS(AbstractQueuedSynchronizer)的理解?

谈谈你对 AQS(AbstractQueuedSynchronizer)的理解?

引言:

"线程安全"是每一个 Java 程序员绕不开的关键词。你是否也曾在代码中使用过 ReentrantLockCountDownLatchSemaphore,却对它们内部的实现一知半解?作为一名有 8年开发经验的 Java 工程师 ,我想和你聊聊这些并发工具背后的核心 ------ AbstractQueuedSynchronizer(简称 AQS) 。这是一个你了解之后就会忍不住想说一句:"这设计,真香!"


一、AQS 是什么?为什么要了解它?

在日常的业务开发中,我们经常会遇到如下场景:

  • 限流:控制并发访问某个资源的线程数量(如:短信接口、支付接口)
  • 任务协调:多个线程等待一个共同条件完成后再执行(如:并发初始化)
  • 独占资源:某一时刻只能有一个线程访问某段代码(如:库存扣减)

这些场景中,SemaphoreCountDownLatchReentrantLock 等工具类频繁登场。而它们的底层实现几乎都基于同一个类:AbstractQueuedSynchronizer

AQS 是 J.U.C(java.util.concurrent)中一个非常核心的同步框架,提供了一种基于 FIFO 队列 的线程同步机制。它本身并不直接实现同步逻辑,而是通过子类实现具体的同步语义。


二、业务场景:模拟一个限流器

假设我们在做一个电商系统,需要限制同时进行支付操作的用户数为 3。超过的用户需要等待。

这时候我们可以使用 Semaphore,而 Semaphore 的核心正是基于 AQS 实现的。


三、从业务到源码:一步步剖析 AQS

3.1 使用场景代码示例

java 复制代码
import java.util.concurrent.Semaphore;

public class PaymentService {
    // 允许最多3个线程同时执行支付
    private final Semaphore semaphore = new Semaphore(3);

    public void pay(String user) {
        try {
            semaphore.acquire(); // 获取许可,占用一个名额
            System.out.println(user + " 正在支付...");
            Thread.sleep(2000); // 模拟支付耗时
            System.out.println(user + " 支付完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release(); // 释放许可
        }
    }

    public static void main(String[] args) {
        PaymentService service = new PaymentService();
        for (int i = 1; i <= 10; i++) {
            final String user = "用户" + i;
            new Thread(() -> service.pay(user)).start();
        }
    }
}

3.2 Semaphore 源码架构简析

java 复制代码
public class Semaphore implements java.io.Serializable {
    // 内部使用Sync表示同步器
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        Sync(int permits) {
            setState(permits); // 初始许可数
        }

        final int getPermits() {
            return getState();
        }

        // 尝试获取许可
        protected final boolean tryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 || compareAndSetState(available, remaining)) {
                    return remaining >= 0;
                }
            }
        }

        // 释放许可
        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (compareAndSetState(current, next))
                    return true;
            }
        }
    }

    // 非公平实现
    static final class NonfairSync extends Sync {
        NonfairSync(int permits) {
            super(permits);
        }
    }

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public void release() {
        sync.releaseShared(1);
    }
}

3.3 AQS 核心机制

  • state:一个 int 类型的共享变量,用来表示同步状态(如剩余许可数)
  • 队列:当线程获取失败时,会被加入一个 CLH 队列进行排队
  • 模板方法模式:AQS 提供了 acquire、release 等公共方法,底层调用子类实现的 tryAcquire、tryRelease 等方法

四、自己实现一个简单的锁(基于 AQS)

我们可以基于 AQS 自定义一个不可重入的独占锁,来更深入理解它的使用。

java 复制代码
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyLock {
    private final Sync sync = new Sync();

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            // state == 0 表示当前未被占用
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0 || getExclusiveOwnerThread() != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1 && getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }
}

使用自定义锁

csharp 复制代码
public class TestMyLock {
    private static final MyLock lock = new MyLock();

    public static void main(String[] args) {
        Runnable task = () -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 获取了锁");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + " 释放了锁");
            }
        };

        for (int i = 0; i < 5; i++) {
            new Thread(task).start();
        }
    }
}

五、总结

  • AQS 是 Java 并发包中的基石,几乎所有同步工具都基于它。
  • 它的核心思想是使用一个共享变量(state)结合队列等待机制,让线程在高并发下能有序、公平地竞争资源。
  • 理解 AQS,不仅能更好地使用已有的并发工具,也能根据业务场景实现自己的同步器。

六、结语

作为一名有多年开发经验的程序员,理解原理是一种责任,也是一种乐趣。AQS 虽然看起来复杂,但其实设计非常优雅。一旦你真正理解了这个框架,很多并发问题就能迎刃而解。

如果你觉得本文对你有帮助,欢迎点赞、收藏、转发。

相关推荐
用户91453633083918 分钟前
ThreadLocal详解:线程私有变量的正确使用姿势
后端
用户40993225021217 分钟前
如何在FastAPI中实现权限隔离并让用户乖乖听话?
后端·ai编程·trae
阿星AI工作室28 分钟前
n8n教程:5分钟部署+自动生AI日报并写入飞书多维表格
前端·人工智能·后端
郝同学的测开笔记28 分钟前
深入理解 kubectl port-forward:快速调试 Kubernetes 服务的利器
后端·kubernetes
Ray6639 分钟前
store vs docValues vs index
后端
像污秽一样1 小时前
软件开发新技术复习
java·spring boot·后端·rabbitmq·cloud
Y_3_71 小时前
Netty实战:从核心组件到多协议实现(超详细注释,udp,tcp,websocket,http完整demo)
linux·运维·后端·ubuntu·netty
小雄Ya1 小时前
Auth01|常见三种登录认证机制
后端·go
颛顼1 小时前
【源码分析】:从零拆解bs_worker的3层核心架构
后端·搜索引擎
yihuiComeOn2 小时前
【大数据高并发核心场景实战】 - 数据持久化之冷热分离
java·后端