队列同步器(AQS)介绍

发布于2025-03-01
字数: 6355
面试
多线程

队列同步器(AQS)介绍

队列同步器AbstractQueuedSynchronizer,下文简称同步器,是用于构建锁、同步组件的底层框架,使用了一个int成员变量来表示同步状态,内置了FIFO队列完成获取资源的线程排队安排。

队列同步器的使用方式是继承,源码中的注释使用也推荐使用继承。子类通过继承重写方法来控制同步状态。一般来说子类推荐定义为自定义同步组件的静态内部类,同步器本身不实现任何同步接口,只是定义了用于获取同步状态和释放的方法供自定义组件使用,同步器支持独占获取同步状态和共享获取同步状态,这样方便实现不同类型的同步组件,比如ReentrantLock、ReentrantReadWriteLock等。

java 复制代码
// 源码中的使用demo
class BooleanLatch {
    private static class Sync extends AbstractQueuedSynchronizer {
        boolean isSignalled() {
            return getState() != 0;
        }

        protected int tryAcquireShared(int ignore) {
            return isSignalled() ? 1 : -1;
        }

        protected boolean tryReleaseShared(int ignore) {
            setState(1);
            return true;
        }
    }

    private final Sync sync = new Sync();

    public boolean isSignalled() {
        return sync.isSignalled();
    }

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

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

AQS接口和实例

AQS提供了三类接口:

第一类:3个访问和修改同步状态的方法

  • getState():获取当前同步状态
  • setState():设置当前同步状态
  • compareAndSetState(int expect, int update):CAS修改当前状态

第二类:可重新的5个方法

  • isHeldExclusively():该线程是否正在独占资源,只有用到condition才需要实现
  • tryAcquire(int arg):尝试以独占模式获取资源,成功返回true,否则false
  • tryRelease():尝试释放独占资源,成功返回true,否则false
  • tryAcquireShared:尝试以共享方式获取资源,负数表示失败;0表示成功,但是没有可使用资源了;正数表示成功且有剩余资源
  • tryReleaseShared(int arg):共享方式,尝试释放资源,成功返回true,否则false

第三类:9个模板方法

方法 作用
void acquire(int arg) 独占式获取同步状态,如果当前线程能成功获取,则该方法返回,否则进入同步队列等待
void aquireInterruptibly(int arg) 与acquire相同,但是可以响应中断
boolean tryAcquireNanos(int arg, long nanos) aquireInterruptibly基础上增加超市机制,指定时间获取同步状态失败返回false,否则true
void aquireShared(int arg) 共享获取同步状态,未获取到进入等待队列
void aquireSharedInterruptibly(int arg) aquireShared增加响应中断
boolean tryAcquireSharedNanos(int arg, long nanos) aquireSharedInterruptibly增加超时机制
boolean release(int arg) 独占式释放同步状态,释放后会唤醒等待队列的第一个节点
boolean releaseShared(int arg) 共享式释放同步状态
Collection getQueueThreads() 获取等待在同步队列的线程集合

示例

java 复制代码
class Mutex implements Lock, java.io.Serializable {
    private static class Sync extends AbstractQueuedSynchronizer {
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public boolean tryAcquire(int acquires) {
            assert acquires == 1;
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        
        protected boolean tryRelease(int releases) {
            assert releases == 1;
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        
        Condition newCondition() {
            return new ConditionObject();
        }

   
        private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0);
        }
    }
    
    private final Sync sync = new Sync();

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

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

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

    public Condition newCondition() {
        return sync.newCondition();
    }

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

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

AQS实现分析

同步队列

  1. 队列节点:节点Node是构成同步队列的基础,获取同步状态失败的线程会被包装成一个节点加入同步队列的尾部

    Node属性介绍

    属性 描述
    int waitStatus 等待状态,有多个状态
    1、CANCELLED:值为1,由于在同步队列中等待的线程超时或者被中断,需要从同步队列中取消等待,节点状态变成该状态后不会再发生改变
    2、SIGNAL:值为-1,后继节点的线程处于等待状态,如果当前节点线程释放同步状态或者被取消,就会通知后继节点
    3、CONDITION:值-2,节点在等待队列中,节点线程等待Condition。其他线程对Condition调用signal方法后,该节点将从等待队列转移到同步队列中,加入到同步状态的获取中
    4、PROPAGATE:值-3,表示下一个共享同步状态获取将会无条件传播
    Node prev 前驱节点
    Node next 后继节点
    Node nextWaiter 等待队列中的后继节点
    Thread thread 获取同步状态的线程
  2. 队列结构

  3. **新节点加入:**有新节点准备加入时,需要保证线程安全,同步器提供了一个基于CAS的设置尾节点方法compareAndSetTail(Node expectm Node update),只有设置成功后新加入节点才能与之前的尾节点建立关联。

  4. **首节点释放:**同步队列是FIFO,首节点就是成功获取同步状态的节点,释放的时候会唤醒后继节点,后继节点获取同步状态成功后会将自己设置为首节点

独占式同步获取和释放

获取同步状态时,同步器维护一个同步队列,获取失败的都会加入队列并且自旋;释放的时候会调用tryRelease方法释放并且唤醒后继节点。移除节点需要满足后继节点获取同步状态且设置为新节点

独占式获取同步状态流程:

image-20250301113012812

共享式同步获取实现

acquireShared方法中,同步器会调用tryAcquireShared方法尝试获得同步状态,tryAcquireShared方法返回值大于大于0时表示能够获取同步状态;释放是调用releaseShared方法。

独占式超时获取与释放

调用doAcquireNanos(int arg, long nanosTimeout)方法可以超时获取同步状态,成功获取true,失败返回false;如果nanosTimeout设置小于等于spinForTimeoutThreshold则不会使该线程超时等待,而是直接进入自旋状态。

独占式超时获取同步状态流程图