java并发编程(十七)带你了解什么是面试官常说的AQS

前面我们学习了ReentrantLock,其底层就是用了AQS实现的,应该先讲这一章节,但是当时给忘了,现在给补上吧。

关于ReentrantLock的学习,可以参考:https://www.jianshu.com/p/edec5185196d

AbstractQueuedSynchronizer是阻塞式锁,以及同步器组件的实现框架。是JDK中实现并发编程的核心,它提供了一个基于FIFO队列,平时我们工作中经常用到的ReentrantLock,CountDownLatch等都是基于它来实现的。

一、初识AQS

首先我们还是从前面学习的ReentrantLock入手,看看其代码结构是什么样的:

ReentrantLockl类图

从上图可以看到以下几点:

  • ReentrantLock实现接口Lock(抽象接口)
  • ReentrantLock有三个内部类,分别是FrairSync、NonfairSync、Sync,且FrairSync、NonfairSync继承自Sync。
  • Sync继承AbstractQueuedSynchronizer
  • AbstractQueuedSynchronizer有两个内部类,分别是Node、ConditionObject。
  • AbstractQueuedSynchronizer继承自AbstractOwnableSynchronizer(抽象类,提供独占线程的get和set)。

AQS有如下的特点:

  • 用 state 属性来表示资源的状态,包含独占状态和共享状态,对应公平锁和非公平锁。子类需要定义如何维护这个状态,控制如何获取锁和释放锁,如上面图中的关系,公平锁和非公平锁需要各自去维护这个state,达到获取和释放锁的目的。

    • getState - 获取 state 状态
    • setState - 设置 state 状态
    • compareAndSetState - 使用CAS设置状态
    • 独占模式:只有一个线程能够访问资源
    • 共享模式:允许多个线程访问资源
  • 提供了基于 FIFO 的等待队列,类似于前面讲Synchronized原理提到的 Monitor 的 EntryList

  • 使用条件变量来实现等待队列、线程唤醒机制,同时支持多个条件变量,类似于前面讲Synchronized原理提到的 Monitor 的 WaitSet

  • 【公平锁】与【非公平锁】:二者的区别主要在于获取锁是否和排队顺序有关。当锁呗一个线程持有,其他尝试获取锁的线程会被挂起,加到等待队列中,先被挂起的在队列的最前端。当锁被释放,需要通知队列中的线程。作为公平锁,会先唤醒队列最前端的线程;而非公平锁会唤醒所有线程,通过竞争去获取锁,后来的线程有可能获得锁。

二、 源码分析

下面我们通过源码剖析其本质是什么样的。

首先在脑海中有个印象,AQS维护了两个对个队列,一个是同步队列,一个是阻塞队列。

Node可以说是【同步队列】和【阻塞队列】的节点。

2.1 Node源码剖析

  static final class Node {
    // 模式,分为共享与独占
    // 共享模式
    static final Node SHARED = new Node();
    // 独占模式
    static final Node EXCLUSIVE = null;        
    // 结点状态
    // CANCELLED,值为1,表示当前的线程被取消
    // SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
    // CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
    // PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;        

    // 结点状态
    volatile int waitStatus;        
    // 前驱结点
    volatile Node prev;    
    // 后继结点
    volatile Node next;        
    // 结点所对应的线程
    volatile Thread thread;        
    // 下一个等待者
    Node nextWaiter;
    
    // 结点是否在共享模式下等待
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    
    // 获取前驱结点,若前驱结点为空,抛出异常
    final Node predecessor() throws NullPointerException {
        // 保存前驱结点
        Node p = prev; 
        if (p == null) // 前驱结点为空,抛出异常
            throw new NullPointerException();
        else // 前驱结点不为空,返回
            return p;
    }
    
    // 无参构造函数
    Node() {    // Used to establish initial head or SHARED marker
    }
    
    // 构造函数,被addWaiter使用
     Node(Thread thread, Node mode) {    // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    
    // 构造函数
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

2.2 ConditionObject源码剖析

实现了condition接口,关于condition的学习后面会介绍,在学习ReentrantLock时也介绍了其使用方式。

代码较多,直接从上向下看吧:

 // 内部类
public class ConditionObject implements Condition, java.io.Serializable {
    // 版本号
    private static final long serialVersionUID = 1173984872572414699L;
    
    // condition队列的头结点
    private transient Node firstWaiter;
    
    // condition队列的尾结点
    private transient Node lastWaiter;

    /**
     *  构造函数
     */
    public ConditionObject() { }

    /**
     * 添加新的waiter到wait队列
     */
    private Node addConditionWaiter() {
        // 定义尾结点是t
        Node t = lastWaiter;
       // 尾结点不为空,并且尾结点的状态不为CONDITION(默认是-2,表示当前节点在conditionObject等待队列中)
        if (t != null && t.waitStatus != Node.CONDITION) { 
            // 清除状态不为CONDITION的结点,对firstWaiter和lastWaiter重新赋值
            unlinkCancelledWaiters(); 
            // 将最后一个结点重新赋值给t
            t = lastWaiter;
        }
        // 新建一个结点
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        // 尾结点为空
        if (t == null) 
            // 设置condition队列的头结点
            firstWaiter = node;
        else 
            // 设置为节点的nextWaiter域为node结点
            t.nextWaiter = node;
        // 更新condition队列的尾结点
        lastWaiter = node;
        return node;
    }

    /**
     *  移除或转移头结点到sync队列,直到没有取消的或者空为止
     */
    private void doSignal(Node first) {
        // 循环
        do {
            // 将下一个节点设为首节点,如果为空
            if ( (firstWaiter = first.nextWaiter) == null) 
                // 设置尾结点为空
                lastWaiter = null;
            // 设置first结点的nextWaiter域
            first.nextWaiter = null;
        } while (!transferForSignal(first) &&
                 (first = firstWaiter) != null); // 将结点从condition队列转移到sync队列失败并且condition队列中的头结点不为空,一直循环
    }

    /**
     * 转移所有等待队列的节点到同步队列
     */
    private void doSignalAll(Node first) {
        // condition队列的头结点尾结点都设置为空
        lastWaiter = firstWaiter = null;
        // 循环
        do {
            // 获取first结点的nextWaiter域结点
            Node next = first.nextWaiter;
            // 设置first结点的nextWaiter域为空
            first.nextWaiter = null;
            // 将first结点从condition队列转移到sync队列
            transferForSignal(first);
            // 重新设置first
            first = next;
        } while (first != null);
    }

    /**
     * 过滤掉状态不为CONDITION的节点
     * 对firstWaiter和lastWaiter重新赋值
     **/
    private void unlinkCancelledWaiters() {
        // 获取condition队列头结点
        Node t = firstWaiter;
        // 获取一个尾结点是null
        Node trail = null;
        while (t != null) {
            // 获取下一个结点
            Node next = t.nextWaiter;
            // 头结点的状态不为CONDTION状态
            if (t.waitStatus != Node.CONDITION) { 
                // 设置t节点的下一个等待者为空
                t.nextWaiter = null;
                if (trail == null) // trail为空,首次进来一定为空
                    // 重新设置condition队列的头结点
                    firstWaiter = next;
                else 
                    // 设置trail结点的nextWaiter域为next结点
                    trail.nextWaiter = next;
                if (next == null) // next结点为空
                    // 设置condition队列的尾结点
                    lastWaiter = trail;
            }
            else // t结点的状态为CONDTION状态
                // 设置trail结点
                trail = t;
            // 设置t结点
            t = next;
        }
    }

    /**
     * 实现Condition接口的signal方法
     */
    public final void signal() {
        if (!isHeldExclusively()) // 不被当前线程独占,抛出异常
            throw new IllegalMonitorStateException();
        // 保存condition队列头结点
        Node first = firstWaiter;
        if (first != null) // 头结点不为空
            // 唤醒一个等待线程,将头结点从阻塞队列移除,添加到同步队列
            doSignal(first);
    }

    /**
     * 实现Condition的signalAll方法,唤醒所有线程
     */
    public final void signalAll() {
        if (!isHeldExclusively()) // 不被当前线程独占,抛出异常
            throw new IllegalMonitorStateException();
        // 保存condition队列头结点
        Node first = firstWaiter;
        if (first != null) // 头结点不为空
            // 唤醒所有等待线程,将头结点从阻塞队列移除,添加到同步队列
            doSignalAll(first);
    }

    /**
     * 与await()区别在于,使用await方法,调用interrupt()中断后会报错,而该方法不会报错。
     */
    public final void awaitUninterruptibly() {
        // 添加一个结点到等待队列
        Node node = addConditionWaiter();
        // 获取释放的状态
        int savedState = fullyRelease(node);
        boolean interrupted = false;
        while (!isOnSyncQueue(node)) { // 
            // 阻塞当前线程
            LockSupport.park(this);
            if (Thread.interrupted()) // 当前线程被中断
                // 设置interrupted状态
                interrupted = true; 
        }
        if (acquireQueued(node, savedState) || interrupted) // 
            selfInterrupt();
    }

    /**
     *  等待,当前线程在接到信号或被中断之前一直处于等待状态
     */
    public final void await() throws InterruptedException {
        // 当前线程被中断,抛出异常
        if (Thread.interrupted()) 
            throw new InterruptedException();
        // 将当前线程包装成Node,尾插入到等待队列中
        Node node = addConditionWaiter();
        // 释放当前线程所占用的lock,在释放的过程中会唤醒同步队列中的下一个节点
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
            // 当前线程进入到等待状态
            LockSupport.park(this);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) // 检查结点等待时的中断类型
                break;
        }
        // 自旋等待获取到同步状态(即获取到lock)
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null) // clean up if cancelled
            unlinkCancelledWaiters();
        // 处理被中断的情况
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }

    /**
     * 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态
     */
    public final long awaitNanos(long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        Node node = addConditionWaiter();
        int savedState = fullyRelease(node);
        final long deadline = System.nanoTime() + nanosTimeout;
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
            if (nanosTimeout <= 0L) {
                transferAfterCancelledWait(node);
                break;
            }
            if (nanosTimeout >= spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
            nanosTimeout = deadline - System.nanoTime();
        }
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
        return deadline - System.nanoTime();
    }

    /**
     * 等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
     */
    public final boolean awaitUntil(Date deadline)
            throws InterruptedException {
        long abstime = deadline.getTime();
        if (Thread.interrupted())
            throw new InterruptedException();
        Node node = addConditionWaiter();
        int savedState = fullyRelease(node);
        boolean timedout = false;
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
            if (System.currentTimeMillis() > abstime) {
                timedout = transferAfterCancelledWait(node);
                break;
            }
            LockSupport.parkUntil(this, abstime);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
        return !timedout;
    }

    /**
     * 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等              
     * 效于:awaitNanos(unit.toNanos(time)) > 0
     */
    public final boolean await(long time, TimeUnit unit)
            throws InterruptedException {
        long nanosTimeout = unit.toNanos(time);
        if (Thread.interrupted())
            throw new InterruptedException();
        // 1. 将当前线程包装成Node,尾插入到等待队列中
        Node node = addConditionWaiter();
        // 2. 释放当前线程所占用的lock,在释放的过程中会唤醒同步队列中的下一个节点
        int savedState = fullyRelease(node);
        final long deadline = System.nanoTime() + nanosTimeout;
        boolean timedout = false;
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
            if (nanosTimeout <= 0L) {
                timedout = transferAfterCancelledWait(node);
                break;
            }
            if (nanosTimeout >= spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
            nanosTimeout = deadline - System.nanoTime();
        }
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
        return !timedout;
    }

2.3 锁的获取和释放

整个AQS的设计理念就是通过state字段来实现锁的获取和释放,锁有主要分为公平锁和非公平锁。

2.3.1 公平锁

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            // 继承自AQS的方法,内部先调用tryAcquire获取锁,获取失败则添加下城到等待队列当中
            acquire(1);
        }

        /**
         * 公平锁版本的tryAcquire
         */
        protected final boolean tryAcquire(int acquires) {
            // 获取当前线程
            final Thread current = Thread.currentThread();
            // 获取锁的状态
            int c = getState();
            // 0表示锁没有被持有
            if (c == 0) {
                // 判断当前等待队列是否有节点在等待,没有才去竞争
                if (!hasQueuedPredecessors() &&
                    // 比较并替换状态
                    compareAndSetState(0, acquires)) {
                    // 设置当前线程为独占线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                // 此处表示锁重入
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

2.3.2 非公平锁

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * 立即获取锁,失败会加入等待队列
         */
        final void lock() {
            // 通过CAS尝试获取锁,成功则设置当前线程独占
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 否则加入等待队列·····································································································································
                acquire(1);
        }

        /** 
          * 非公平锁版本的tryAcquire
          */
        protected final boolean tryAcquire(int acquires) {
            // 走其父类Sync的默认nonfairTryAcquire
            return nonfairTryAcquire(acquires);
        }
    }

2.3.3 Syc子类

这是公平锁和非公平锁的父类,提供统一的tryRelease方法释放锁

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * 提供非公平版本的快捷路径
         */
        abstract void lock();

        /**
         * 非公平锁获取,默认就是非公平锁
         */
        final boolean nonfairTryAcquire(int acquires) {
            // 获取当前线程
            final Thread current = Thread.currentThread();
            // 获取当前锁的状态
            int c = getState();
            // 0表示没有被占用
            if (c == 0) {
                // CAS占用,成功则设置当前线程为独占锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 当前线程是独占锁,表示锁重入
            else if (current == getExclusiveOwnerThread()) {
                // 状态 + 1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                // 设置当前状态
                setState(nextc);
                return true;
            }
            return false;
        }

         /**
         * 释放锁
         */
        protected final boolean tryRelease(int releases) {
            // 当前状态 减去 释放的数量
            int c = getState() - releases;
            // 如果当前线程不是占有锁的线程,抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 当全部释放后,状态为0,取消独占线程
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            // 设置状态为0,返回释放成功
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively() {
            // 当前线程是否是锁持有者
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        // 获取当前持有者
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        // 获取持有数,只有是线程持有者才能获取
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
    }

3.3.4 acquire 和 release

在AQS当中还有两个核心方法:

  • acquire():获取锁,这是实际锁调用上锁的真正方法,前面的try开头的知识尝试获取锁,即使失败也不会添加到等待队列。
    public final void acquire(int arg) {
      // 尝试获取
      if (!tryAcquire(arg) &&
          // 尝试获取成功,以独占方式添加到等待队列,并不断地尝试占有锁知道成功
          acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          selfInterrupt();
  }
  • release():释放锁,这是实际释放锁的方法,会调用锁自定义的同步器实现的tryRelease方法:
        /**
          * 尝试释放,成功后返回true
          */
        public final boolean release(int arg) {
          if (tryRelease(arg)) {
              Node h = head;
              if (h != null && h.waitStatus != 0)
                  unparkSuccessor(h);
              return true;
          }
          return false;
      }
    

后面我们自定义不可重入锁,来看看同步器和锁的关系是什么样的,加深理解。

2.4 简单总结

到此为止,通过阅读前面的源码内容,我们可以有如下的总结:

  • 锁的释放和获取都是围绕 【state】来做的,0表示未持有锁,1表示独占,大于一表示锁重入
  • 获取锁的姿势如下:
      // 如果获取锁失败
      if (!tryAcquire(arg)) {
        // 入队, 可以选择阻塞当前线程 park unpark
      }
    
  • 释放锁的姿势如下:
      // 如果获取锁成功
      if (!tryRelease(arg)) {
        // 让阻塞线程恢复运行
      }
    

三、实践

了解了AQS的结构之后,我们不妨自己动手实践一番。加深理解。

定义一个不可重入锁,需要一个同步器,一个锁,一个测试类

自定义同步器:

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

/**
 * @description: 实现一个不可重入锁 同步器,state最大只能是1
 * @author:weirx
 * @date:2022/1/13 13:49
 * @version:3.0
 */
public class MyLockSynchronizer extends AbstractQueuedSynchronizer {

    @Override
    protected boolean tryAcquire(int acquires) {
        int state = getState();
        if (state == 0) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
        }
        return false;
    }

    @Override
    protected boolean tryRelease(int acquires) {
        int c = getState() - acquires;
        if (c == 0) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        return false;
    }

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

    protected ConditionObject newCondition() {
        return new ConditionObject();
    }
}

自定义锁:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @description: 自定义锁
 * @author:weirx
 * @date:2022/1/13 14:05
 * @version:3.0
 */
public class MyLock implements Lock {

    MyLockSynchronizer myLockSynchronizer = new MyLockSynchronizer();

    @Override
    public void lock() {
        // 尝试获取锁,失败则加入等待队列
        myLockSynchronizer.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        // 尝试获取锁,失败则加入等待队列, 可中断
        myLockSynchronizer.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        // 尝试获取锁,不加入等待队列
        return myLockSynchronizer.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        // 尝试获取锁,不加入等待队列,有实现
        return myLockSynchronizer.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        // 释放锁
        myLockSynchronizer.release(1);
    }

    @Override
    public Condition newCondition() {
        // 条件变量
        return myLockSynchronizer.newCondition();
    }
}

测试锁的效果:

/**
 * @description: 测试
 * @author:weirx
 * @date:2022/1/13 14:24
 * @version:3.0
 */
public class TestMyLock {

    public static void main(String[] args) {
        MyLock myLock = new MyLock();

        new Thread(() -> {
            try {
                myLock.lock();
                System.out.println(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")
                        + " " + Thread.currentThread().getName() + " :acquire lock success");

                // 休眠一秒看效果
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                myLock.unlock();
                System.out.println(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")
                        + " " + Thread.currentThread().getName() + " :release lock success");
            }

        }, "t1").start();

        new Thread(() -> {
            try {
                myLock.lock();
                System.out.println(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")
                        + " " + Thread.currentThread().getName() + " :acquire lock success");
            } finally {
                myLock.unlock();
                System.out.println(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")
                        + " " + Thread.currentThread().getName() + " :release lock success");
            }
        }, "t2").start();
    }
}

结果,ti一秒后释放锁,才会由t2获得锁:

2022-01-13 14:34:56 t1 :acquire lock success
2022-01-13 14:34:57 t2 :acquire lock success
2022-01-13 14:34:57 t1 :release lock success
2022-01-13 14:34:57 t2 :release lock success

测试下不可重入是否好使,只需要在上述测试代码的线程t1中,再次使用myLock.lock()获取一次锁,发现,整个程序被卡住了,只会打印一条信息:

2022-01-13 14:35:56 t1 :acquire lock success

四、关于Condition的补充

本篇没有介绍Condition的具体内容,但是在之前讲解ReentrantLock提到过【条件变量】,可以返回去看这篇文章了解其用法:https://www.jianshu.com/p/edec5185196d


源码学习真是难,看别人说的再多不如自己跟着走一遍,建议同学们参照本文自己跟踪一遍。

你可能感兴趣的:(java并发编程(十七)带你了解什么是面试官常说的AQS)