|
| 1 | +### AQS 简介 |
| 2 | +AQS 是 AbstractQueuedSynchronizer 的简称,即**抽象队列同步器**。AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的同步器,比如我们提到的 ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于 AQS 的。 |
| 3 | + |
| 4 | +### AQS 的数据结构 |
| 5 | +AQS 内部使用了一个 volatile 的变量 state 来作为资源的标识。同时定义了几个获取和改变 state 的 protected 方法,子类可以覆盖这些方法来实现自己的逻辑: |
| 6 | +```java |
| 7 | +getState() |
| 8 | +setState() |
| 9 | +compareAndSetState() |
| 10 | +``` |
| 11 | +这三种均是原子操作,其中 compareAndSetState 的实现依赖于 Unsafe 的 compareAndSwapInt() 方法。 |
| 12 | + |
| 13 | + |
| 14 | + |
| 15 | +AQS 内部使用了一个先进先出的双端队列,并使用了两个指针 head 和 tail 用于标识队列的头部和尾部。但它并不是直接存储线程,而是存储拥有线程的 Node 节点。 |
| 16 | + |
| 17 | +### 资源共享模式 |
| 18 | +资源有两种同步模式: |
| 19 | +1. 独占模式(Exclusive):资源是独占的,一次只能一个线程获取。 |
| 20 | +2. 共享模式(Share):同时可以被多个线程获取,具体的资源个数可以通过参数指定。 |
| 21 | + |
| 22 | +```java |
| 23 | +static final class Node { |
| 24 | + // 标记一个结点(对应的线程)在共享模式下等待 |
| 25 | + static final Node SHARED = new Node(); |
| 26 | + // 标记一个结点(对应的线程)在独占模式下等待 |
| 27 | + static final Node EXCLUSIVE = null; |
| 28 | + |
| 29 | + // waitStatus的值,表示该结点(对应的线程)已被取消 |
| 30 | + static final int CANCELLED = 1; |
| 31 | + // waitStatus的值,表示后继结点(对应的线程)需要被唤醒 |
| 32 | + static final int SIGNAL = -1; |
| 33 | + // waitStatus的值,表示该结点(对应的线程)在等待某一条件 |
| 34 | + static final int CONDITION = -2; |
| 35 | + /*waitStatus的值,表示有资源可用,新head结点需要继续唤醒后继结点(共享模式下,多线程并发释放资源,而head唤醒其后继结点后,需要把多出来的资源留给后面的结点;设置新的head结点时,会继续唤醒其后继结点)*/ |
| 36 | + static final int PROPAGATE = -3; |
| 37 | + |
| 38 | + // 等待状态,取值范围,-3,-2,-1,0,1 |
| 39 | + volatile int waitStatus; |
| 40 | + volatile Node prev; // 前驱结点 |
| 41 | + volatile Node next; // 后继结点 |
| 42 | + volatile Thread thread; // 结点对应的线程 |
| 43 | + Node nextWaiter; // 等待队列里下一个等待条件的结点 |
| 44 | + |
| 45 | + |
| 46 | + // 判断共享模式的方法 |
| 47 | + final boolean isShared() { |
| 48 | + return nextWaiter == SHARED; |
| 49 | + } |
| 50 | + |
| 51 | + Node(Thread thread, Node mode) { // Used by addWaiter |
| 52 | + this.nextWaiter = mode; |
| 53 | + this.thread = thread; |
| 54 | + } |
| 55 | + |
| 56 | + // 其它方法忽略,可以参考具体的源码 |
| 57 | +} |
| 58 | + |
| 59 | +// AQS里面的addWaiter私有方法 |
| 60 | +private Node addWaiter(Node mode) { |
| 61 | + // 使用了Node的这个构造函数 |
| 62 | + Node node = new Node(Thread.currentThread(), mode); |
| 63 | + // 其它代码省略 |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +### AQS 的主要方法源码 |
| 68 | +- isHeldExclusively():该线程是否正在独占资源。只有用到 condition 才需要去实现它; |
| 69 | +- tryAcquire(int):独占方式。尝试获取资源,成功则返回 true,失败则返回 false; |
| 70 | +- tryRelease(int):独占方式。尝试释放资源,成功则返回 true,失败则返回 false; |
| 71 | +- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源; |
| 72 | +- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回 true,否则返回 false。 |
| 73 | + |
| 74 | +上述方法都是 protected 方法,之所以不使用抽象方法的目的是可以灵活的让子类根据需要选择实现某些方法而不是强迫子类实现所有的抽象方法。 |
| 75 | + |
| 76 | +#### 获取资源 |
| 77 | +```java |
| 78 | +public final void acquire(int arg) { |
| 79 | + if (!tryAcquire(arg) && |
| 80 | + acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) |
| 81 | + selfInterrupt(); |
| 82 | +} |
| 83 | + |
| 84 | +private Node addWaiter(Node mode) { |
| 85 | + // 生成该线程对应的Node节点 |
| 86 | + Node node = new Node(Thread.currentThread(), mode); |
| 87 | + // 将Node插入队列中 |
| 88 | + Node pred = tail; |
| 89 | + if (pred != null) { |
| 90 | + node.prev = pred; |
| 91 | + // 使用CAS尝试,如果成功就返回 |
| 92 | + if (compareAndSetTail(pred, node)) { |
| 93 | + pred.next = node; |
| 94 | + return node; |
| 95 | + } |
| 96 | + } |
| 97 | + // 如果等待队列为空或者上述CAS失败,再自旋CAS插入 |
| 98 | + enq(node); |
| 99 | + return node; |
| 100 | +} |
| 101 | + |
| 102 | +// 自旋CAS插入等待队列 |
| 103 | +private Node enq(final Node node) { |
| 104 | + for (;;) { |
| 105 | + Node t = tail; |
| 106 | + if (t == null) { // Must initialize |
| 107 | + if (compareAndSetHead(new Node())) |
| 108 | + tail = head; |
| 109 | + } else { |
| 110 | + node.prev = t; |
| 111 | + if (compareAndSetTail(t, node)) { |
| 112 | + t.next = node; |
| 113 | + return t; |
| 114 | + } |
| 115 | + } |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +final boolean acquireQueued(final Node node, int arg) { |
| 120 | + boolean failed = true; |
| 121 | + try { |
| 122 | + boolean interrupted = false; |
| 123 | + // 自旋 |
| 124 | + for (;;) { |
| 125 | + final Node p = node.predecessor(); |
| 126 | + // 如果node的前驱结点p是head,表示node是第二个结点,就可以尝试去获取资源了 |
| 127 | + if (p == head && tryAcquire(arg)) { |
| 128 | + // 拿到资源后,将head指向该结点。 |
| 129 | + // 所以head所指的结点,就是当前获取到资源的那个结点或null。 |
| 130 | + setHead(node); |
| 131 | + p.next = null; // help GC |
| 132 | + failed = false; |
| 133 | + return interrupted; |
| 134 | + } |
| 135 | + // 如果自己可以休息了,就进入waiting状态,直到被unpark() |
| 136 | + if (shouldParkAfterFailedAcquire(p, node) && |
| 137 | + parkAndCheckInterrupt()) |
| 138 | + interrupted = true; |
| 139 | + } |
| 140 | + } finally { |
| 141 | + if (failed) |
| 142 | + cancelAcquire(node); |
| 143 | + } |
| 144 | +} |
| 145 | +``` |
| 146 | +> parkAndCheckInterrupt方法内部使用了LockSupport.park(this),LockSupport 类是 Java 6 引入的一个类,提供了基本的线程同步原语。LockSupport 实际上是调用了 Unsafe 类里面的函数: |
| 147 | +1. park(boolean isAbsolute, long time):阻塞当前线程; |
| 148 | +2.unpark(Thread jthread):使给定的线程停止阻塞。 |
| 149 | + |
| 150 | +**所以结点进入等待队列后,是调用park使它进入阻塞状态的。只有头结点的线程是处于活跃状态的。** |
| 151 | + |
| 152 | +获取资源的方法除了 acquire 外,还有以下三个: |
| 153 | +- acquireInterruptibly:申请可中断的资源(独占模式); |
| 154 | +- acquireShared:申请共享模式的资源; |
| 155 | +- acquireSharedInterruptibly:申请可中断的资源(共享模式) |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | +#### 释放资源 |
| 160 | +```java |
| 161 | + |
| 162 | +public final boolean release(int arg) { |
| 163 | + if (tryRelease(arg)) { |
| 164 | + Node h = head; |
| 165 | + if (h != null && h.waitStatus != 0) |
| 166 | + unparkSuccessor(h); |
| 167 | + return true; |
| 168 | + } |
| 169 | + return false; |
| 170 | +} |
| 171 | + |
| 172 | +private void unparkSuccessor(Node node) { |
| 173 | + // 如果状态是负数,尝试把它设置为0 |
| 174 | + int ws = node.waitStatus; |
| 175 | + if (ws < 0) |
| 176 | + compareAndSetWaitStatus(node, ws, 0); |
| 177 | + // 得到头结点的后继结点head.next |
| 178 | + Node s = node.next; |
| 179 | + // 如果这个后继结点为空或者状态大于0 |
| 180 | + // 通过前面的定义我们知道,大于0只有一种可能,就是这个结点已被取消 |
| 181 | + if (s == null || s.waitStatus > 0) { |
| 182 | + s = null; |
| 183 | + // 等待队列中所有还有用的结点,都向前移动 |
| 184 | + for (Node t = tail; t != null && t != node; t = t.prev) |
| 185 | + if (t.waitStatus <= 0) |
| 186 | + s = t; |
| 187 | + } |
| 188 | + // 如果后继结点不为空, |
| 189 | + if (s != null) |
| 190 | + LockSupport.unpark(s.thread); |
| 191 | +} |
| 192 | +``` |
0 commit comments