跳转到内容
唯一赫兹
返回

AQS 与 JUC 常见组件

AQS

首先明确我们希望构建出怎样的同步器:

  1. 状态管理:下游可以自由定义同步状态,以此实现多种同步逻辑,比如可重入、独占/共享等
  2. 等待队列:锁被占用时,后来的线程需要休眠并排队等待
  3. 主动唤醒:锁释放后,应该主动唤醒等待队列中的线程

AQS 在以上基础上还提供:

  1. 中断机制:支持在等待锁时响应中断信号,实现应用的优雅退出或任务取消
  2. 超时机制:防止线程无限期阻塞,导致请求积压

AQS 通过一个锁状态和一个 FIFO 队列(双向链表)来管理多线程的同步状态。

AQS 的非静态成员变量如下:

private transient volatile Node head;  // 队列头节点哨兵
private transient volatile Node tail;  // 队列尾节点哨兵
private volatile int state;            // 同步状态AbstractQueuedSynchronizer.java

其中 Node 表示一个正在等待的线程,其结构如下:

abstract static class Node {
    volatile Node prev;  // 前驱节点
    volatile Node next;  // 后继节点
    Thread waiter;       // 指向一个等待线程
    volatile int status; // 节点状态
    // ......省略了后续的方法
}AbstractQueuedSynchronizer.java

AQS 提供了 tryAcquire 方法供下游自定义获取锁的逻辑,在 acquire 中调用 tryAcquire 并封装等待队列的逻辑。

// 子类必须重写 tryAcquire 方法
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 尝试获取锁,如果获取失败,则调用封装好的等待队列的逻辑
public final void acquire(int arg) {
    if (!tryAcquire(arg))
        acquire(null, arg, false, false, false, 0L);
}

// 包私有方法
final int acquire(
        Node node,             // 队列中的线程节点
        int arg,               // 请求参数
        boolean shared,        // 共享锁
        boolean interruptible, // 是否可中断
        boolean timed,         // 是否需要超时
        long time
) {
    //...
}AbstractQueuedSynchronizer.java

总结

ReentrantLock

ReentrantLock 的结构如下

public class ReentrantLock implements Lock, java.io.Serializable {

    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 内部类,继承AQS
    }
    static final class NonfairSync extends Sync {
        // 非公平锁
    }
    static final class FairSync extends Sync {
        // 公平锁
    }
    private final Sync sync; // 唯一的成员变量,内置锁相关逻辑

    // 根据传入的 bool 判断用哪个锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    public void lock() {
        sync.lock();
    }
    public void unlock() {
        sync.release(1);
    }
    // ...
}ReentrantLock.java

AQS 中的 stateReentrantLock 中的定义为「重入次数」。

内部定义了一个同步器 Sync 及其两个子类 FairSync NonfairSync,两个子类内部重写了 tryAcquire 方法并实现了重入相关逻辑,以 NonfairSync 为例:

abstract static class Sync extends AbstractQueuedSynchronizer {
    final void lock() {
        if (!initialTryLock())  // 尝试快速获取锁,如果失败再走 AQS 的逻辑
            acquire(1);
    }
}
static final class NonfairSync extends Sync {

    final boolean initialTryLock() {
        Thread current = Thread.currentThread();
        if (compareAndSetState(0, 1)) { // 先假设 state == 0,直接 CAS
            setExclusiveOwnerThread(current); // 保存 owner 为当前线程
            return true;
        } else if (getExclusiveOwnerThread() == current) { // owner 就是当前线程,说明重入
            int c = getState() + 1;
            if (c < 0) // c 是 int 类型,为负数说明溢出了
                throw new Error("Maximum lock count exceeded");
            setState(c);
            return true;
        } else
            return false;
    }

    // 先执行 initialTryLock,然后才在 acquire 内部被调用
    protected final boolean tryAcquire(int acquires) {
        // 只在第一次进入锁时设置 owner
        if (getState() == 0 && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
}ReentrantLock.java

关于公平锁/非公平锁,二者的差距很小:

// FairSync 的 tryAcquire
protected final boolean tryAcquire(int acquires) {
    if (getState() == 0 && !hasQueuedPredecessors() &&  // 判断队列中是否有正在等待的线程
        compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}ReentrantLock.java

FairSyncNonfairSync 的差距只有一行,公平锁先判断等待队列中是否有线程,如果没有才争抢,如果有则直接失败。

CountDownLatch

先看 CountDownLatch 的用法:

public static void main(String[] args) throws InterruptedException {
    int n = 10; // 有 10 个前置任务
    CountDownLatch latch = new CountDownLatch(n);  // 创建一个 latch,目标是完成十个任务
    for (int i = 0; i < 10; ++i) {
        int num = i;
        Thread task = new Thread(() -> {
            System.out.println(num);
            latch.countDown(); // 完成一个任务
        });
        task.start();
    }
    boolean success = latch.await(5, TimeUnit.SECONDS);  // 等待所有任务完成,5 秒超时
    System.out.println(success);
}Main.java

CountDownLatch 一样包含一个继承 AQS 的子类 Sync,内部重写了 tryAcquireSharedtryReleaseShared,其中的 state 表示需要完成的任务数量。

AQS 中定义 tryAcquireShared 的返回值:

以下为 CountDownLatch 的部分源码:

public class CountDownLatch {

        // 初始化 state
        Sync(int count) {
            setState(count);
        }

        // 在 acquireSharedInterruptibly 中被调用
        protected int tryAcquireShared(int acquires) {
            // 任务全部完成则返回 1,表示成功获取锁并唤醒所有等待的主线程
            // 返回 -1 表示获取锁失败,主线程继续等待
            return (getState() == 0) ? 1 : -1;
        }

        // 在 releaseShared 中被调用
        protected boolean tryReleaseShared(int releases) {
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

    private final Sync sync;

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

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

    public void countDown() {
        sync.releaseShared(1);
    }
}CountDownLatch.java


上一篇
【JVM 4】CMS | G1 | ZGC
下一篇
Synchronized 锁机制:从无锁到重量级锁