首页 元宇宙

AQS深度剖析:打造高性能并发组件的同步利器

分类:元宇宙
字数: (1583)
阅读: (9838)
内容摘要:AQS深度剖析:打造高性能并发组件的同步利器,

在构建高并发系统时,锁是不可或缺的同步工具。然而,锁的实现细节往往隐藏着性能瓶颈。相信大家都遇到过这样的场景:在高并发环境下,应用程序的响应时间 резко 上升,CPU 占用率却很低,这时很有可能就是锁竞争导致的。例如,使用简单的 synchronized 关键字,虽然简单易用,但在竞争激烈的情况下,性能会急剧下降。更高级的锁,比如 ReentrantLock 提供了更多的功能,但也增加了使用的复杂度。

那么,如何才能构建既高效又易于使用的同步组件呢?答案就是:理解并运用 AbstractQueuedSynchronizer (AQS)。

AQS深度剖析:打造高性能并发组件的同步利器

AbstractQueuedSynchronizer (AQS) 的核心思想

AQS,即抽象队列同步器,是 Java 并发包 java.util.concurrent 中的核心组件。它提供了一个用于构建锁和同步器的框架,而不是一个具体的锁实现。ReentrantLockSemaphoreCountDownLatch 等并发工具都是基于 AQS 构建的。

AQS深度剖析:打造高性能并发组件的同步利器

AQS 的核心思想是:

AQS深度剖析:打造高性能并发组件的同步利器
  1. State (状态):AQS 维护一个 volatile int state 变量,用于表示同步状态。不同的同步器可以根据自己的需求来定义状态的含义,例如,ReentrantLock 使用 state 来表示锁的持有次数,Semaphore 使用 state 来表示剩余的许可证数量。
  2. FIFO 队列:AQS 内部维护一个 FIFO 队列,用于存放等待获取同步状态的线程。当一个线程尝试获取同步状态失败时,它会被放入队列中等待,直到有其他线程释放同步状态。
  3. CLH 变体:AQS 的队列实际上是 CLH 队列的一个变体。CLH 队列是一种基于链表的 FIFO 队列,它的每个节点都持有一个指向前驱节点的引用。AQS 在 CLH 队列的基础上进行了一些优化,例如使用 compareAndSet 操作来保证线程安全,并引入了 head 和 tail 节点来简化队列的操作。

AQS 的工作流程

AQS 的工作流程大致如下:

AQS深度剖析:打造高性能并发组件的同步利器
  1. 线程尝试获取同步状态。如果获取成功,则直接返回。
  2. 如果获取失败,则将当前线程封装成一个节点,放入 FIFO 队列的尾部。
  3. 队列中的线程会不断尝试获取同步状态,直到获取成功或者被取消。
  4. 当一个线程释放同步状态时,它会唤醒队列中的下一个线程,让其尝试获取同步状态。

AQS 的源码解析

AQS 的源码比较复杂,但我们可以抓住几个关键点进行分析:

  • tryAcquire(int arg):尝试以独占模式获取同步状态。子类需要重写该方法,并根据自己的需求来实现获取同步状态的逻辑。如果获取成功,则返回 true,否则返回 false。
  • tryRelease(int arg):尝试以独占模式释放同步状态。子类需要重写该方法,并根据自己的需求来实现释放同步状态的逻辑。如果释放成功,则返回 true,否则返回 false。
  • tryAcquireShared(int arg):尝试以共享模式获取同步状态。子类需要重写该方法,并根据自己的需求来实现获取同步状态的逻辑。如果获取成功,则返回一个非负数,否则返回一个负数。
  • tryReleaseShared(int arg):尝试以共享模式释放同步状态。子类需要重写该方法,并根据自己的需求来实现释放同步状态的逻辑。如果释放成功,则返回 true,否则返回 false。
  • acquire(int arg):以独占模式获取同步状态,如果获取失败,则阻塞等待。
  • acquireShared(int arg):以共享模式获取同步状态,如果获取失败,则阻塞等待。
  • release(int arg):以独占模式释放同步状态。
  • releaseShared(int arg):以共享模式释放同步状态。

下面是一个简单的 AQS 使用示例,实现一个简单的互斥锁:

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class SimpleMutex {

    private static class Sync extends AbstractQueuedSynchronizer {
        // 独占模式,state 为 0 表示未锁定,1 表示已锁定
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) { // CAS 操作,尝试将 state 从 0 设置为 1
                setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程为独占线程
                return true;
            } else {
                return false;
            }
        }

        @Override
        protected boolean tryRelease(int arg) {
            if (!isHeldExclusively()) { // 如果当前线程不是独占线程,则抛出异常
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null); // 释放独占线程
            setState(0); // 将 state 设置为 0
            return true;
        }

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

    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1); // 独占模式获取锁
    }

    public void unlock() {
        sync.release(1); // 释放锁
    }

    public boolean isLocked() {
      return sync.isHeldExclusively();
    }

    public static void main(String[] args) throws InterruptedException {
        SimpleMutex mutex = new SimpleMutex();
        Runnable task = () -> {
            mutex.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " acquired the lock");
                Thread.sleep(100); // 模拟临界区操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                mutex.unlock();
                System.out.println(Thread.currentThread().getName() + " released the lock");
            }
        };

        Thread t1 = new Thread(task, "Thread-1");
        Thread t2 = new Thread(task, "Thread-2");

        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }
}

实战避坑经验总结

  • 避免长时间持有锁:长时间持有锁会导致其他线程阻塞等待,降低并发性能。尽量缩短临界区的代码执行时间。
  • 选择合适的锁模式:根据实际需求选择独占锁或共享锁。如果多个线程可以同时读取共享资源,则可以使用共享锁,提高并发性能。
  • 避免死锁:死锁是指多个线程互相等待对方释放资源,导致程序无法继续执行。可以通过避免循环等待、按顺序获取锁等方式来避免死锁。
  • 合理设置公平性:AQS 支持公平锁和非公平锁。公平锁会按照线程请求锁的顺序来分配锁,而非公平锁则允许线程插队。在某些情况下,非公平锁可以提高吞吐量,但也可能导致某些线程饥饿。
  • 监控 AQS 的性能: 使用 JConsole、VisualVM 等工具监控 AQS 的性能指标,例如锁的竞争情况、等待队列的长度等,以便及时发现和解决性能问题。如果发现锁竞争激烈,可以考虑使用更细粒度的锁,或者采用无锁化的数据结构和算法。 此外,在使用 Nginx 做反向代理时,也要关注连接池的大小和 upstream 的配置,避免因后端服务处理能力不足而导致请求堆积。

掌握 AbstractQueuedSynchronizer 是构建高性能并发组件的关键。通过深入理解 AQS 的原理和使用方法,我们可以更好地设计和实现各种锁和同步器,从而提高并发系统的性能和可靠性。

AQS深度剖析:打造高性能并发组件的同步利器

转载请注明出处: 键盘上的咸鱼

本文的链接地址: http://m.acea2.store/blog/027151.SHTML

本文最后 发布于2026-04-19 22:10:28,已经过了8天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 广东肠粉 2 天前
    学习了!最近在优化一个高并发服务,正好用得上 AQS 的知识。之前只知道用 ReentrantLock,现在可以考虑自己实现一个更适合业务场景的锁了。
  • 芒果布丁 4 天前
    学习了!最近在优化一个高并发服务,正好用得上 AQS 的知识。之前只知道用 ReentrantLock,现在可以考虑自己实现一个更适合业务场景的锁了。
  • 选择困难症 1 天前
    讲的真好,AQS 这块一直模棱两可,这篇文章深入浅出,受益匪浅。