头图

AbstractQueuedSynchronizer(AQS),是阻塞式锁和同步器工具的框架。本文将初步介绍Java中AQS的基本原理,并基于AQS实现自定义阻塞式不可重入锁,以此来演示AQS的使用。下期会以 ReentrantLock 为例,从源码的层面介绍 AQS 的核心实现 acquire() 方法。

AQS 的目标

  • 提供阻塞式获取锁 acquire() 和非阻塞式尝试获取锁 tryAcquire();
  • 提供获取锁超时机制;
  • 通过打断取消机制;
  • 独占机制与共享机制;
  • 条件不满足时的等待机制;

AQS 的设计

  1. 使用 state 表示资源状态

    • 使用 volatile 配合 cas 保证其修改时的原子性;
    • 使用 32bit int 来维护同步状态。
  2. 使用先进先出队列

    • 借鉴 CLH 队列:无锁,使用自旋;快速,无阻塞;
    • 头节点 head 不存储数据,尾节点 指向队列最后一个等待线程。
  3. 使用 park & unpark 来调度线程

    • 可以先 unpark() 再 park();
    • unpark & park 针对线程,而不是同步器,控制粒度更为精细;
    • park() 可通过 interrupt() 打断。
  4. 使用条件变量实现等待、唤醒机制

    • 每个条件变量维护一个条件变量队列;
    • 线程等待:先将线程从等待队列中移除,再将线程加入条件变量队列;
    • 线程唤醒:先将线程从条件变量队列中移除,再将线程加入等待队列。

等待队列和条件变量队列的维护已由 AQS 实现,AQS 的子类只需定义如何维护 state、如何获取和释放锁,主要需要实现的方法如下

  • tryAcquire():尝试获取独占锁;
  • tryRelease():尝试释放独占锁;
  • tryAcquireShared():尝试获取共享锁;
  • tryReleaseShared():尝试释放共享锁;
  • isHeldExclusively():判断当前线程是否持有锁。

AQS 分为独占模式和共享模式,独占模式同时只允许一个线程访问资源,共享模式同时允许多个线程访问资源。

使用 AQS 的主要并发工具类如下图所示,主要包括:ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore。

自定义阻塞式不可重入锁

自定义同步器

自定义独占式同步器需要重写 AQS 的如下方法:

  • tryAcquire():尝试获取锁,约定 state 为 0 表示未加锁,为 1 表示已加锁。
  • tryRelease():尝试释放锁
  • isHeldExclusively():判断当前线程是否持有锁
  • newCondition():创建条件变量
static class ISync extends AbstractQueuedSynchronizer {

    /**
     * 尝试获取锁
     * @param arg 忽略
     */
    @Override
    protected boolean tryAcquire(int arg) {
        if (
            // 将state的值置为1
            compareAndSetState(0, 1)
        ) {
            // 设置owner为当前线程
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    /**
     * 尝试释放锁
     * @param arg 忽略
     */
    @Override
    protected boolean tryRelease(int arg) {
        // 持有锁的线程才可以释放锁
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        // 注意setExclusiveOwnerThread和setState的执行顺序
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    /**
     * 是否持有锁
     */
    @Override
    protected boolean isHeldExclusively() {
        // 判断锁的持有者是否为当前线程
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    /**
     * 创建条件变量
     */
    public Condition newCondition() {
        return new ConditionObject();
    }
}

在 tryRelease() 方法中,注意 setExclusiveOwnerThread() 和 setState() 的执行顺序,state 被 volatile 修饰,而 exclusiveOwnerThread 没有,因此,后设置 state 可保证 exclusiveOwnerThread 的设置对其他线程可见。

private transient Thread exclusiveOwnerThread;
private volatile int state;

自定锁

使用自定义同步器实现自定义阻塞式不可重入锁。

class ILock implements Lock {
    
    // 自定义同步器
    private ISync sync = new ISync();

    /**
     * 加锁
     * 加锁失败则进入队列等待
     */
    @Override
    public void lock() {
        sync.acquire(1);
    }

    /**
     * 加锁
     * 加锁失败则进入队列等待,在加锁的过程中可被打断
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    /**
     * 尝试加锁
     * 加锁失败则返回false
     */
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    /**
     * 尝试加锁
     * 指定时间内加锁失败则返回false
     */
    @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
    @NonNull
    public Condition newCondition() {
        return sync.newCondition();
    }
}

自定义锁中使用到的 AQS 中的方法

public abstract class AbstractQueuedSynchronizer {
    // 尝试获取锁
    public final void acquire(int arg) {
        // 获取锁失败
        if (!tryAcquire(arg))
            // 加入阻塞队列
            acquire(null, arg, false, false, false, 0L);
    }
    // 尝试获取锁(可打断)
    public final void acquireInterruptibly(int arg) throws InterruptedException {
        if (
            // 线程被打断
            Thread.interrupted() ||
            (
                // 获取锁失败
                !tryAcquire(arg) && 
                // 加入阻塞队列的过程中出现问题并成功取消获取锁
                acquire(null, arg, false, true, false, 0L) < 0)
            )
            throw new InterruptedException();
    }
    // 释放锁
    public final boolean release(int arg) {
        // 释放锁成功
        if (tryRelease(arg)) {
            // 唤醒阻塞的线程
            signalNext(head);
            return true;
        }
        return false;
    }
}

END

文章文档:公众号 字节幺零二四 回复关键字可获取本文文档。

如果觉得本文对您有一点点帮助,欢迎点赞、转发加关注,这会对我有非常大的帮助,咱们下期见!


字节幺零二四
9 声望5 粉丝

talk is cheap, show me you code!