抽象队列同步器AQS

【Java 并发编程】AQS

基本说明

AQS全称AbstractQueuedSynchronizer ,中文名为抽象队列同步器 ,它是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器(比如:信号量)的一个同步框架。

AQS是一个抽象类,实现了主要的一部分逻辑,还有一些方法需要其子类自行取实现以达到个性化的需求。

AQS主要做如下三件事:

  1. 同步状态的维护

    什么是同步状态呢?其实AQS并不管,它只是提供了state变量用于记录锁状态,不同的实现锁状态的意义不同,比如:是否获取到锁,锁的重入次数,获取到锁的线程的次数等等。

    AQS提供了state的变量以及获取、修改锁的方法,子类可以使用这些方法实现对同步状态的修改。

  2. **同步队列(SyncQueue)**的维护

    AQS通过CLH双向链表 实现的同步队列(SyncQueue来维护线程的等待,这里的等待是获取锁的等待,比如:对于排他锁的ReentrantLock如果A线程获取到了锁,那么其他线程在A释放锁之前请求获取锁,ReentrantLock为了保证同一时间只能有一个线程访问共享资源,所以A线程后面的线程都需要进 入同步队列(SyncQueue

    AQS提供了对于同步队列(SyncQueue入队出队等方法,这些是无需子类去实现的。

  3. **条件队列(ConditionQueue)**的维护

    AQS实现了类似Synchronizedwait/notify的机制,条件队列(ConditionQueue也是一个使用内部类Node作为载体的单线链表结构的队列。

AQS支持两种锁机制:

  1. 独占模式:只允许有一个线程能够访问共享资源。

  2. 独立模式:允许多个线程同时访问共享资源。

作为一个抽象队列同步器 这样一个基础框架,很多锁都使用了AQS,比如:ReentranantLockSemaphoreCountDownLatchCylicBarrierReentrantReadWriteLock等。

ReentranantLock就是一个典型的独占锁模式。

SemaphoreCountDownLatchCylicBarrier则是使用的共享模式。

ReentrantReadWriteLock则是同时使用了独占和共享两种模式。

同步状态的维护

AQS中定义了一个使用volitale修改的共享变量state,该变量就是同步状态,或者说使用该变量实现同步状态,同时提供了针对state变量的获取以及修改方法,源码如下:

getState:获取state的状态。

setState:设置state的状态。

compareAndSetState:使用CAS乐观锁机制设置state状态。

同步队列(SyncQueue)的维护

AQS通过CLH双向链表实现的**同步队列(SyncQueue)**来维护线程的等待(区别开条件等待)。

  1. 当线程获取对象锁失败的时候,AQS会将线程组装为一个Node对象,将其加入CLH这个**同步队列(SyncQueue)**的尾部,同时会阻塞当前线程。
  2. 当前面线程持有的锁释放的时候,头节点会再次尝试获取对象锁。

CLH**同步队列(SyncQueue)**如下图所示:

绿色Node1持有对与红色共享资源的锁,灰色的Node则处于等待状态,等到Node1的锁释放则后续线程才有机会获取到锁,访问共享资源。

为什么使用双向链表?使用双向链表是基于实际情况的,因为**同步队列(SyncQueue)**中会有删除中间节点的需求(比如线程请求获取锁,但是随后取消或者异常中断,此时需要删除该节点),基于这个需求,双向链表更加高效,时间复杂度低。

对于链表,删除某一个节点必须知道其前驱节点,如果使用单链表则需要从头遍历链表才能找到前驱节点,时间复杂度是O(n),而使用双向链表,则时间复杂度是O(1)

条件队列(ConditionQueue)的维护

AQS使用使用条件队列(ConditionQueue来实现线程间的等待通知 机制,AQS的一个内部类ConditionObjectCondition的一个实现类,每一个Condition包含一个等待队列,这个等待队列是一个单链表,在每一个Condition中都会有两个指针,一个指向头节点,一个指向尾节点。

  1. 当调用Conditionawait方法的时候会阻塞线程,将线程存储到**条件队列(ConditionQueue)**的尾部。
  2. 当调用signal方法的时候,就会唤醒**条件队列(ConditionQueue的头节点线程,将其移动到 同步队列(SyncQueue)**中,等待获取锁。

**条件队列(ConditionQueue)**的结构大致如下:

**条件队列(ConditionQueue)**只有在独占模式下才会被访问。

为什么使用单链表?因为对于条件等待,无需删除中间节点。

同步队列(SyncQueue)与等待队列

同步队列SyncQueue)和等待队列 ()两者是配合使用的,一个锁可以包含多个Condition实力,一个Condition内部就包含一个条件队列(ConditionQueue (为什么?因为一个Condition下可能有多个线程在等待。)

  1. 当一个线程调用await的时候,节点将从**同步队列(SyncQueue移动到 条件队列(ConditionQueue)**中,
  2. 反之,当调用signal或者signalAll的时候,当前Condition的**条件队列(ConditionQueue中的的线程节点就会被唤醒,将其从 条件队列(ConditionQueue移动到 同步队列(SyncQueue中,尝试去获取锁,如果获取到,那么就没问题,如果获取不到,则会将其放到 同步队列(SyncQueue)**的队尾。

Node的结构

Node是CLH双向链表实现的同步队列(SyncQueue)的节点的载体,它内部包含了线程以及线程等待等一些信息。Node与线程是一对一的关系。

下图是Node的源码定义:

Node类中主要有如下属性:

  1. 模式

    java 复制代码
    /** Marker to indicate a node is waiting in shared mode */
    // 表示Node里的线程以共享的模式等待锁
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    // 表示Node里的线程以独占的模式等待锁
    static final Node EXCLUSIVE = null;

    上述两个变量代表线程以什么模式等待锁,SHARED代表共享模式,EXCLUSIVE代表独占模式。

  2. 等待状态 等待状态waitStatus是一个特别重要的属性,它代表的是当前节点在队列中的状态,主要有如下四种状态(也可以说是五种,默认是0)。

    java 复制代码
    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

    CANCELLED:取消状态,代表线程获取锁的请求已经取消了。

    SIGNAL:表示后继节点线程处于等待状态,本节点线程释放锁后,要通知后继节点获取锁。

    CONDITION:线程调用await方法,会将其设置为CONDITION状态,并将其放入**条件队列(ConditionQueue)**中。

    PROPAGATE:当前线程处于SHARED共享模式下,此字段才会被使用,EXCLUSIVE排他模式下无效,表示下一个acquireShared需要向后传播。

  3. Thread对象

    一个Node节点就是代表了一个线程获取锁的状态信息描述。

  4. 条件队列(ConditionQueue)的指针

    nextWaiter是在通过Condition实现等待、唤醒的时候使用到的,在独立模式下,它指向条件队列(ConditionQueue ,在共享模式下则值固定等于SHARED(因为只有独立模式下符合条件等待、唤醒的场景)。

  5. 同步队列(SyncQueue)的指针

    prenext

属性总结:

  1. prenext是**同步队列(SyncQueue)**双向链表的前驱和后继指针
  2. nextWaiter是**条件队列(ConditionQueue)**单链表的后继指针(独立模式),在共享模式下它是一个固定值SHARED
  3. waitStatus同时包含了**同步队列(SyncQueue 条件队列(ConditionQueue)**下的节点状态,0是默认值。

AQS源码

独占模式同步状态获取

独占是同步获取锁的方法是acquire,源码如下:

java 复制代码
// 独占模式获取锁
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 尝试以独占模式获取锁,需要子类自行实现。
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

// 将当前线程组装为一个Node,参数是mode是获取方式(独立或者共享), 将其加入到同步队列(SyncQueue)中
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node); // 入队方法
    return node;
}

// 入队方法
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

// 将线程自己打断
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

逻辑:

①:首先调用tryAcquire方法(该方法AQS只定义并未实现,需要由子类实现其具体功能 )尝试以独占模式获取锁,如果获取到则直接返回true,如果,如果获取锁失败,返回false

②:如果①中尝试获取锁失败,就会通过调用addWaiter(Node.EXCLUSIVE)方法,该方法首先将向前线程组装为一个独占模式的Node对象,然后调用enq(node);将节点添加到**同步队列(SyncQueue)**的队尾(内部使用自旋锁,会一直自选,直到加入到队尾)。

③:当在②步骤中成功加入到队尾后,执行acquireQueued方法,该方法会使得同步队列中的节点不断地在自旋判断其前驱节点是不是头节点,如果是则尝试获取同步状态,否则会阻塞节点中的线程。

流程图如下:

独占模式同步状态释放

独占模式同步状态释放使用release方法,源码如下:

java 复制代码
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 唤醒后继节点
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

①:调用需要子类自行首先的tryRelease方法尝试释放同步状态。

②:如果释放成功,则继续唤醒头结点的后继节点线程。返回true

③:否则返回false

流程图如下:

共享模式同步状态获取

java 复制代码
// 共享模式同步状态获取
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
// 尝试共享模式获取同步状态,需要子类自行实现
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
// 使用共享模式获取同步状态
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED); // 添加共享模式的节点
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

通过tryAcquireShared方法尝试获取锁,如果返回值大不小于0,表示能够获得锁,如果返回值小于0,就需要通过doAcquireShared不断自旋获得锁。

共享模式同步状态释放

共享模式同步状态释放的方法是releaseShared,源码如下:

java 复制代码
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
// 尝试释放共享模式同步状态
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

AQS实现

供子类实现的方法主要有如下五个:

这几种方法前面基本都有介绍过,本处再简单说明下:

text 复制代码
tryAcquire: 独占模式获取同步状态
tryRelease: 独占模式释放同步状态
tryAcquireShared: 共享模式获取同步状态
tryReleaseShared: 共享模式释放同步状态
isHeldExclusively:独占模式下是否被当前前程独占

接下来手动实现一个独占模式的锁,如何实现呢?

  1. 创建一个类MyLock实现Lock接口。
  2. 创建一个同步器类MySync继承AQS,并实现tryAcquiretryReleaseisHeldExclusively方法。
  3. MyLock类中,创建一个MySync类型的属性,并初始化。重写Lock的方法,并调用MySync中的方法。

创建一个类MyLock实现Lock接口

java 复制代码
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {
    @Override
    public void lock() {
        
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

创建一个同步器类MySync继承AQS,并实现tryAcquiretryReleaseisHeldExclusively方法。

java 复制代码
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {
    /**
     * 自定义同步器
     */
    static class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            // 使用cas尝试修改state的值为1
            if (compareAndSetState(0, arg)) {
                setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程拥有访问共享资源的权限
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null); // 移除权限
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

    @Override
    public void lock() {

    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

MyLock类中,创建一个MySync类型的属性,并初始化。重写Lock的方法,并调用MySync中的方法。

java 复制代码
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {
    /**
     * 自定义同步器
     */
    private static class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            // 使用cas尝试修改state的值为1
            if (compareAndSetState(0, arg)) {
                setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程拥有访问共享资源的权限
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null); // 移除权限
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

    private final MySync sync = new MySync();

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

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

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

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

    @Override
    public void unlock() {
        sync.release(0);
    }


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

测试下:

java 复制代码
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        // 定义A线程
        new Thread(() -> {
            try {
                myLock.lock();
                System.out.println(Thread.currentThread().getName() + "加锁");
                TimeUnit.SECONDS.sleep(3L);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "解锁");
                myLock.unlock();
            }
        }, "A").start();

        // 定义B线程
        new Thread(() -> {
            try {
                myLock.lock();
                System.out.println(Thread.currentThread().getName() + "加锁");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "解锁");
                myLock.unlock();
            }
        }, "B").start();
    }
}

运行结果如下图所示:

可以看到虽然A线程首先获得锁,并且等待到了三秒,B线程等A线程释放锁之后才获得锁。

AQS的子类实现有很多,下图是一个简单的概括,图片来源网络。

相关推荐
wn53136 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀1231 小时前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people3 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政8 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
拾光师9 小时前
spring获取当前request
java·后端·spring
Java小白笔记11 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis
JOJO___13 小时前
Spring IoC 配置类 总结
java·后端·spring·java-ee
白总Server14 小时前
MySQL在大数据场景应用
大数据·开发语言·数据库·后端·mysql·golang·php