深入理解Java AQS:从原理到源码分析

文章详细解析了AQS的设计原理,包括队列节点、FIFO结构、state的作用、公平锁与非公平锁,以及如何基于AQS实现ReentrantLock、ReentrantReadWriteLock、Semaphore和CountDownLatch等同步器。AQS是Java并发编程的关键组件,提供了实现同步和锁定的灵活框架。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于AQS(AbstractQueuedSynchronizer)是Java并发包中的一个关键组件,它提供了一种实现同步器(如锁和其他同步工具)的框架,用于实现各种同步器(如ReentrantLock、Semaphore等)。AQS的核心思想是基于FIFO队列实现阻塞和唤醒线程,以及维护同步状态。

AQS的设计原理

AQS 使用一个整数(state)表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。它的主要使用方式是继承,子类通过继承AQS并实现它的几个protected方法来管理其状态(acquire 和 release)并控制线程的排队和阻塞。

1、队列节点 Node 和 FIFO队列结构

AQS 内部通过一个叫做Node的静态内部类来表示队列中的每一个等待线程。而这些节点组成了一个双向链表,这就是AQS的等待队列。每一个节点都包含了线程引用、状态和前驱及后继节点的连接。AQS的队列被设计成FIFO,确保了首个节点通常是等待时间最长的节点。

推荐参考:并发编程之:AQS源码解析

加锁过程与等待队列的关系

  1. 线程尝试获取锁:当线程调用AQS的acquire()方法尝试获取锁时,首先会检查当前锁的状态。如果锁已经被其他线程持有,则当前线程无法立即获取锁。
  2. 加入等待队列:获取锁失败的线程会被包装成一个节点,并通过CAS操作将其加入到等待队列的尾部。这个过程中,线程会自旋尝试加入队列,直到成功为止。
  3. 自旋与阻塞:加入队列后,线程会进入自旋状态,不断检查锁的状态。如果锁仍然被其他线程持有,线程会继续自旋;如果锁被释放,则尝试获取锁。如果自旋一段时间后仍然无法获取锁,线程可能会被挂起(阻塞),等待被唤醒。

解锁过程与等待队列的关系

  1. 线程释放锁:当线程持有锁并执行完相关操作后,会调用AQS的release()方法释放锁。
  2. 唤醒等待队列中的线程:释放锁的过程中,AQS会检查等待队列中是否有等待的线程。如果有,它会唤醒队列头部的线程(通常是最早等待的线程),给予其获取锁的机会。
  3. 线程重新尝试获取锁:被唤醒的线程会重新尝试获取锁。如果此时锁已经被其他线程持有,线程会再次加入到等待队列中;如果锁已经释放,线程则可以成功获取锁并继续执行。

2、state 的作用

state是AQS中的核心变量,它用来表示同步器的状态。不同的同步器可以使用state来表示不同的意义。例如,ReentrantLock用它来表示锁的持有次数;Semaphore用它来表示当前可用的许可证数量。在 ReentrantReadWriteLock 中,state 的高 16 位表示写锁的重入次数,低 16 位表示读锁的数量。在 CountDownLatch 中,state 表示还需要被等待的倒数计数。

3、公平锁与非公平锁

AQS支持两种锁模式:公平锁和非公平锁。公平锁意味着当锁可用时,AQS会按照等待时间最长的线程来分配(即FIFO),而非公平锁则允许新线程插队,可能会忽略排队时间较长的线程。非公平锁的吞吐量一般比公平锁要高。

AQS 源码解析

为了深入理解AQS的工作机制,我们将分析几个关键的方法。

1、Node节点

Node是AQS内部定义的一个帮助类,表示等待队列中的一个节点,它的定义如下:

static final class Node {
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
    // ...
}

waitStatus 表示节点的状态。
prev 指向前一个节点。
next 指向下一个节点。
thread 是执行线程的引用。
nextWaiter 用于构建条件队列。

2、acquire(int)

获取资源的过程,它的基本逻辑是如果当前状态允许,则尝试获取资源。如果失败,则构建节点并进入等待队列,可能会循环直到成功获取。

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire 是由子类实现的方法,在锁实现中,它尝试直接获取资源。
addWaiter 方法将当前线程封装成节点并加入等待队列。
acquireQueued 方法是使线程在队列中等待,必要时阻塞,并尝试获取资源。

3、release(int)

当一个线程完成了对资源的使用后,它会调用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;
}

tryRelease 是由子类实现的方法,用于释放资源。
unparkSuccessor 用于唤醒等待队列中的后续节点。

4、自旋(Spin)

在阻塞队列中,当一个线程尝试获取但是未能成功时,AQS会把这个线程放入队列。在队列中的线程会不断地检查是否能够成为头结点并获取资源,这个过程叫做自旋。

5、公平性与 FIFO

公平锁的实现考虑到了队列的FIFO顺序,在公平锁中,如果队列中有比当前线程更早的线程在等待,则当前的线程将加入队列等待。非公平锁允许插队,在性能上可能有一定优势,但在高并发下可能会导致某些线程饿死。

公平性和非公平性的区别在于tryAcquire方法和acquire方法的实现。
在公平模式下,tryAcquire方法会首先检查队列中是否有等待线程,如果有,则不会尝试获取锁,而是返回false,等待线程会按照顺序逐个获取锁。
而在非公平模式下,tryAcquire方法会直接尝试获取锁,不考虑等待队列中的线程顺序。

protected final boolean isHeldExclusively() {
    // 该方法在子类中实现,用来查询当前线程是否独占该锁
}

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

以上是用于扩展的几个方法。对于独占模式,需要实现tryAcquire和tryRelease方法;对于共享模式,则需要实现tryAcquireShared和tryReleaseShared。

基于AQS实现的几种同步器

1、ReentrantLock:可重入独占锁

ReentrantLock 是一种可重入的互斥(独占)锁,它允许已经获取到锁的线程再次获取锁而不会被阻塞。这种锁的实现基于AQS。

这里是一个精简版本的ReentrantLock获取锁(lock)和释放锁(unlock)的基本原理:

public class ReentrantLock {
    // 内部持有AQS的一个子类
    private final Sync sync;
    
    // Sync是AQS的一个子类
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 获取锁 
        abstract void lock();
        
        // 尝试释放锁
        protected final 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;
        }
    }
    
    // ... 其他方法省略
}

2、ReentrantReadWriteLock:可重入读写锁

ReentrantReadWriteLock允许多个线程同时读取资源,但只允许一个线程写入资源。适用于多读少写场景。它也是基于AQS实现的。

其内部主要有两个类:ReadLock和WriteLock,这两个类都是通过控制AQS中的状态来实现锁的功能:

public class ReentrantReadWriteLock {
    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;
    
    // AQS的子类实现
    private final Sync sync;
    
    static final class Sync extends AbstractQueuedSynchronizer {
        // 写锁的获取和释放
        // ...
        
        // 读锁的获取和释放
        // ...
    }
    
    public class ReadLock {
        // 获取读锁
        public void lock() {
            sync.acquireShared(1);
        }
        // 释放读锁
        public void unlock() {
            sync.releaseShared(1);
        }
    }

    public class WriteLock {
        // 获取写锁
        public void lock() {
            sync.acquire(1);
        }
        // 释放写锁
        public void unlock() {
            sync.release(1);
        }
    }
}

3、Semaphore:信号量

Semaphore 信号量用于同时多个线程访问场景,可以初始化时设置同时访问线程数。其核心方法是acquire()和release(),它们分别用于获取和释放许可。

public class Semaphore {
    private final Sync sync;
    
    // AQS的子类实现
    static abstract class Sync extends AbstractQueuedSynchronizer {
        // 获取许可
        abstract void acquireShared(int permits);
        // 释放许可
        final boolean releaseShared(int permits) {
            // ...
        }
    }
    
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

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

4、CountDownLatch:倒计时门闩

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待

public class CountDownLatch {
    private final Sync sync;
    
    // AQS的子类实现
    private static final class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
            setState(count);
        }
        
        // 减少锁存器计数
        public void countDown() {
            releaseShared(1);
        }
        
        // 等待直到锁存器计数为零
        public void await() throws InterruptedException {
            acquireSharedInterruptibly(1);
        }
    }
    
    public CountDownLatch(int count) {
        this.sync = new Sync(count);
    }

    public void countDown() {
        sync.countDown();
    }
    
    public void await() throws InterruptedException {
        sync.await();
    }
}

以上代码只是为了解释概念,并未展示真正的实现细节。在实际的JDK实现中,这些类的内部结构更为复杂,包括了性能优化、状态管理、线程调度和锁升级等机制。您需要直接查看JDK源码以获取完整和准确的实现。

结语

AQS是用于实现锁和同步器的框架。提供了双向链表的node节点等待队列可实现公平和非公平性独占锁和共享锁提供了state变量,在不同的同步器中,代表了不同的含义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值