AQS --- 渐入佳境

作者: 贪挽懒月 | 来源:发表于2021-07-20 16:43 被阅读0次

    上一讲了解了 AQS 是什么,接下来看看它到底是怎样的结构。

    一. 工作原理

    AQS 使用一个 volatile 的 int 类型的成员变量来表示同步状态,通过内置的 FIFO 队列来完成资源获取和排队工作,将每条要去抢占资源的线程封装成一个 node 节点来实现锁的分配,通过 CAS 来完成对 state 值的修改。

    HashMap 进行 put 的时候,也不是直接存储 key value 键值对,而是将 key value 键值对封装成 Node 节点,然后用数组 + 链表 + 红黑树存储 Node。AQS 也类似,将要抢占资源的 Thread 封装成 Node节点。

    二. 相关源码:

    public abstract class AbstractQueuedSynchronizer
        extends AbstractOwnableSynchronizer
        implements java.io.Serializable {
       
        static final class Node {
           ……
           volatile int waitStatus;
           volatile Node prev;
           volatile Node next;
           volatile Thread thread;
           ……
        }
    
        private transient volatile Node head;
        private transient volatile Node tail;
    
        /**
         * The synchronization state.
         */
        private volatile int state;
    }
    

    看到这个是不是就清清楚楚明明白白真真切切了。首先 AQS 外层是 state + CLH 队列,state 表示同步的状态,默认是0,为0时表示可以获取锁,不为0时,线程就得老老实实到队列中排队去;CLH 队列就是一个有头结点和尾结点的双端队列,如下图:

               +------+  prev +-----+       +-----+
          head |      | <---- |     | <---- |     |  tail
               +------+       +-----+       +-----+
    

    AQS 的内层是一个 Node内部类,这个 Node 类主要有两个指针 prev 和 next、一个 waitStatus 表示线程的状、,一个 Thread 类型的变量保存等待的线程。

    三. 从 ReentrantLock 看 AQS:

    之前说了 AQS 是 JUC 并发包的基石,那就从我们接触最多的 ReentrantLock 入手,揭开它的神秘面纱。

    先来看看 ReentrantLock 的结构图:

    结构图

    首先它实现了 Lock 接口,其内部主要是一个 Sync 内部类,这个内部类又有两个子类,一个 FairSync 和一个 NonfairSync,分别用来实现公平锁和非公平锁。而这个 Sync 内部类,又是 AbstractQueuedSynchronizer 的子类。

    1. 我们 new ReentrantLock 的时候做了什么事?

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
     public ReentrantLock() {
         sync = new NonfairSync();
     }
    

    通过这个构造方法可以知道,实际上是构建了一个非公平锁。如果 new 的时候传了 true,调用的构造方法就是:

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
     public ReentrantLock(boolean fair) {
         sync = fair ? new FairSync() : new NonfairSync();
     }
    

    所以传的是 true,构建的就是公平锁。

    2. 公平和非公平有什么区别?

    非公平锁源码:

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    

    公平锁源码:

     protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        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;
    }
    

    乍一看两段代码好像没啥不一样,其实不同之处在,if (c == 0)这段判断中。公平锁多了一个判断条件,即!hasQueuedPredecessors(),看看这个方法的源码:

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    

    这个方法也很简单,首先是头节点不等于尾节点,然后就是头节点的下一个节点为空或者头节点的下一个节点保存的 Thread 不等于当前的 Thread。简单地说就是看队列中有没有除了当前 Thread 以为的 Thread 在等待获取锁,有就返回 true,否则返回 false。所以公平锁就是多了这个判断,其他都一样。

    下一篇文章将会从源码层面分析 ReentrantLock 的加锁过程,敬请期待!

    相关文章

      网友评论

        本文标题:AQS --- 渐入佳境

        本文链接:https://www.haomeiwen.com/subject/mlllrltx.html