麒麟人生 發表於 2025-7-6 01:03:00

天天用lock,不好奇他到底怎么工作的吗 —从ReentrantLock 到AQS

<p>新手学习,若有不对,欢迎大佬 <s>调教🥰🥰🥰</s></p>
<h3 id="reentrantlock">ReentrantLock</h3>
<p>我们经常用的 <code>*ReentrantLock*</code>是干什么的呢 我认为这是一个前台/门面(类似设计模式中的门面模式)根据我们的入参创建一个<code>FairSync</code> OR <code>NonfairSync</code> 。<code>sync</code> 担任锁的lock()和release()。</p>
<pre><code class="language-java">    private final Sync sync;
   
    public ReentrantLock() {
      sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
      sync = fair ? new FairSync() : new NonfairSync();
    }

</code></pre>
<p>那有人可能就问了啥是公平锁(<code>FairSync</code>)? 啥是非公平锁(<code>NonfairSync</code>)?</p>
<p>就拿商场试吃举例子,前者就是大家都好好排队,后者是新来的看试吃小样还有,直接拿走<strong>不参与排队</strong>,那显然后面的人就会<em>饥饿</em> 啊。那非公平锁有什么意义呢。想象一下,当商场人满为患了,你去排到试吃的后面都要挤过来,挤过去。显然你在全局上影响了商场的客流动,如果你直接去 <em>偷袭!<s>(马保国音)</s></em> 显然在商场全局上来说是最优的。</p>
<h2 id="加锁">加锁</h2>
<h3 id="aqs入队">AQS入队</h3>
<p>因为<code>FairSync</code> 和<code>NonfairSync</code> 差的不是很大, 我们就着重讲<code>NonfairSync</code></p>
<p>那你说那我缺的这块<code>FairSync</code>谁给我补啊,<em>想要就自己来拿( 指自己看源码)<s>维吉尔音</s></em></p>
<pre><code class="language-java">   
//java.util.concurrent.locks.ReentrantLock
    static final class NonfairSync extends Sync {
      private static final long serialVersionUID = 7316153563782823691L;

      final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
      }
</code></pre>
<p>可见如果CAS成功线程就直接获得锁了,不成功就走了 <code>acquire()</code> 因为<code>Sync extends AbstractQueuedSynchronizer</code>让我们来看看<code>acquire()</code></p>
<pre><code class="language-java">// java.util.concurrent.locks.AbstractQueuedSynchronizer
   public final void acquire(int arg) {
      if (!tryAcquire(arg) &amp;&amp;
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
</code></pre>
<p><code>tryAcquire()</code> 获取锁失败进入AQS等待队列</p>
<p>AQS终于是露出鸡脚了<code>acquireQueued(addWaiter(Node.EXCLUSIVE), arg))</code></p>
<p>AQS(AbstractQueuedSynchronizer)抽象队列同步器,名字是不是很高大上,我们别管</p>
<p>就是商场老大爷、老大妈排队购物(先进先出的双向链表)。</p>
<p>让我们看看node具有的属性</p>
<pre><code class="language-java">    static final class Node {
      // 共有锁?
      static final Node SHARED = new Node();
      // 独占锁?
      static final Node EXCLUSIVE = null;

      // 线程被取消
      static final int CANCELLED =1;
      // 线程处于激活态
      static final int SIGNAL    = -1;
      // 线程在等待中
      static final int CONDITION = -2;
      /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
      static final int PROPAGATE = -3;

</code></pre>
<p>让我们再看看<code>addWaiter()</code> 通过CAS确保成功加入最后一个节点。</p>
<pre><code class="language-java">    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);   //对AQS进行初始化再加入
      return node;
    }
</code></pre>
<p><code>enq()</code> 对队列进行初始化,添加一个虚拟节点(避免空指针)</p>
<pre><code class="language-java">    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;
                }
            }
      }
    }
</code></pre>
<h3 id="aqs出队">AQS出队</h3>
<p>让我们回到 <code>acquire()</code></p>
<pre><code class="language-java">    public final void acquire(int arg) {
      if (!tryAcquire(arg) &amp;&amp;
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
</code></pre>
<p>买菜大妈也挺急的,要排队就会催前面快点,于是拍拍前面的人,说往前催一下。(少数情况)前面的人也很急,看着时间来不及烧菜了,就自暴自弃,直接离开了,空出了位置。</p>
<pre><code class="language-java">    final boolean acquireQueued(final Node node, int arg) {
      boolean failed = true;
      try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head &amp;&amp; tryAcquire(arg)) {
                  setHead(node);
                  p.next = null; // help GC
                  failed = false;
                  return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &amp;&amp;
                  parkAndCheckInterrupt())
                  interrupted = true;
            }
            
      // 外部中断,或线程取消等待
      } finally {
            if (failed)
                cancelAcquire(node);
      }
    }
</code></pre>
<p>后面的人看到前面有空位,就往前走再催前面的人。看到前面的人已经在催前面的人,他就不催了,催玩之后自己就能待机了(干着急也没用)。</p>
<p>为什么会<em>看到前面的人已经在催前面的人</em> 可能有两个节点被同时加入</p>
<pre><code class="language-java">    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
      int ws = pred.waitStatus;
      if (ws == Node.SIGNAL)// 前面的人已经在问了
            return true;
      if (ws &gt; 0) {      // 取消节点,空出位置,往前挪
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus &gt; 0);
            pred.next = node;
      } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
      }
      return false;
    }
</code></pre>
<h2 id="解锁">解锁</h2>
<p>我们来看看锁的释放队列队列为空则调用<code>unparkSuccessor(h)</code> ,为什么 waitState以等于0做标记,且看下文</p>
<pre><code class="language-java">    public final boolean release(int arg) {
      if (tryRelease(arg)) {
            Node h = head;
            if (h != null &amp;&amp; h.waitStatus != 0) // 检查AQS是否初始化,或队列是否为空
                unparkSuccessor(h);
            return true;
      }
      return false;
    }
</code></pre>
<p>waitState等于0可简单看做,已经完成了他作为解锁信号的职责,同时这和 -1是不一样的,</p>
<p>-1 是未知的往前催(不知道前面好没好),0是肯定的说前面有一个空位,并且是head指针自发的,不会传递。</p>
<pre><code class="language-java">private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws &lt; 0)
      compareAndSetWaitStatus(node, ws, 0); // 重置 waitStatus为 0
    Node s = node.next;
    if (s == null || s.waitStatus &gt; 0) {
      s = null;
      for (Node t = tail; t != null &amp;&amp; t != node; t = t.prev) // 如果你观察到了这段的奇怪之处,我也没办法解释,看了文章也看到不是很明白,就不误导人了。相关内容在 java.util.concurrent.locks.AbstractQueuedSynchronizer#cancelAcquire
            if (t.waitStatus &lt;= 0)
                s = t;
    }
    if (s != null)
      LockSupport.unpark(s.thread); // 唤醒下一个线程
}
</code></pre>
<p>队列被 <code>unpark()</code> 唤醒,队伍可以向前移动了</p>
<p>如果觉得有帮到你</p>
<p>点个赞再走呗baby 🥰🥰🥰</p>
<hr>
<p><em>参考文章:</em></p>
<p>不可不说的Java“锁”事<br>
从ReentrantLock的实现看AQS的原理及应用</p><br><br>
来源:https://www.cnblogs.com/many-bucket/p/18968134
頁: [1]
查看完整版本: 天天用lock,不好奇他到底怎么工作的吗 —从ReentrantLock 到AQS