SynchronousQueue的put方法底层源码

一、SynchronousQueue的put方法底层源码

SynchronousQueue 的 put 方法用于将元素插入队列。由于 SynchronousQueue 没有实际的存储空间,put 方法会阻塞,直到有消费者线程调用 take 方法移除元素


1、put 方法的作用

  • 将元素插入队列。

  • 如果没有消费者线程等待,当前线程会阻塞,直到有消费者线程移除元素。

  • 该方法不会返回任何值,也不会抛出异常(除非线程被中断)。


2、put 方法的源码

以下是 SynchronousQueue 中 put 方法的源码(基于 JDK 17):

可以看到,put 方法的核心逻辑是通过 transferer.transfer 方法实现的。transferer 是 SynchronousQueue 的内部组件,负责实际的数据传输


3、transferer.transfer 方法

transferer 是一个抽象类,有两个实现:

  • TransferStack:用于非公平模式。

  • TransferQueue:用于公平模式。


以下是 TransferStack 和 TransferQueue 中 transfer 方法的通用逻辑:


(1)TransferStack.transfer 方法

  E transfer(E e, boolean timed, long nanos) {
      SNode s = null; // 创建一个新节点
      int mode = (e == null) ? REQUEST : DATA; // 判断是生产者还是消费者

      for (;;) {
          SNode h = head; // 获取栈顶节点
          if (h == null || h.mode == mode) { // 如果栈为空或模式匹配
              if (timed && nanos <= 0) { // 如果超时
                  if (h != null && h.isCancelled()) // 如果节点已取消
                      casHead(h, h.next); // 移除已取消的节点
                  else
                      return null; // 返回 null
              } else if (casHead(h, s = snode(s, e, h, mode))) { // 尝试插入新节点
                  SNode m = awaitFulfill(s, timed, nanos); // 等待匹配
                  if (m == s) { // 如果节点被取消
                      clean(s); // 清理节点
                      return null; // 返回 null
                  }
                  if ((h = head) != null && h.next == s) // 如果匹配成功
                      casHead(h, s.next); // 移除匹配的节点
                  return (E) ((mode == REQUEST) ? m.item : s.item); // 返回数据
              }
          } else if (!isFulfilling(h.mode)) { // 如果栈顶节点未完成匹配
              if (h.isCancelled()) // 如果节点已取消
                  casHead(h, h.next); // 移除已取消的节点
              else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) { // 尝试插入新节点
                  for (;;) {
                      SNode m = s.next; // 获取下一个节点
                      if (m == null) { // 如果下一个节点为空
                          casHead(s, null); // 重置栈顶
                          s = null; // 重置节点
                          break;
                      }
                      SNode mn = m.next;
                      if (m.tryMatch(s)) { // 尝试匹配
                          casHead(s, mn); // 移除匹配的节点
                          return (E) ((mode == REQUEST) ? m.item : s.item); // 返回数据
                      } else
                          s.casNext(m, mn); // 移除未匹配的节点
                  }
              }
          } else { // 如果栈顶节点已完成匹配
              SNode m = h.next; // 获取下一个节点
              if (m == null) // 如果下一个节点为空
                  casHead(h, null); // 重置栈顶
              else {
                  SNode mn = m.next;
                  if (m.tryMatch(h)) // 尝试匹配
                      casHead(h, mn); // 移除匹配的节点
                  else
                      h.casNext(m, mn); // 移除未匹配的节点
              }
          }
      }
  }


(2)TransferQueue.transfer 方法

  E transfer(E e, boolean timed, long nanos) {
      QNode s = null; // 创建一个新节点
      boolean isData = (e != null); // 判断是生产者还是消费者

      for (;;) {
          QNode t = tail;
          QNode h = head;
          if (t == null || h == null) // 如果队列未初始化
              continue;

          if (h == t || t.isData == isData) { // 如果队列为空或模式匹配
              QNode tn = t.next;
              if (t != tail) // 如果 tail 已更新
                  continue;
              if (tn != null) { // 如果 tail 未更新
                  advanceTail(t, tn); // 更新 tail
                  continue;
              }
              if (timed && nanos <= 0) // 如果超时
                  return null; // 返回 null
              if (s == null) // 如果节点未初始化
                  s = new QNode(e, isData); // 创建新节点
              if (!t.casNext(null, s)) // 尝试插入新节点
                  continue;

              advanceTail(t, s); // 更新 tail
              Object x = awaitFulfill(s, e, timed, nanos); // 等待匹配
              if (x == s) { // 如果节点被取消
                  clean(t, s); // 清理节点
                  return null; // 返回 null
              }

              if (!s.isOffList()) { // 如果节点未移除
                  advanceHead(t, s); // 更新 head
                  if (x != null) // 如果匹配成功
                      s.item = s; // 标记节点
                  s.waiter = null; // 清除等待线程
              }
              return (x != null) ? (E)x : e; // 返回数据
          } else { // 如果模式不匹配
              QNode m = h.next;
              if (t != tail || m == null || h != head) // 如果队列已更新
                  continue;

              Object x = m.item;
              if (isData == (x != null) || x == m || !m.casItem(x, e)) { // 如果匹配失败
                  advanceHead(h, m); // 移除未匹配的节点
                  continue;
              }

              advanceHead(h, m); // 更新 head
              LockSupport.unpark(m.waiter); // 唤醒等待线程
              return (x != null) ? (E)x : e; // 返回数据
          }
      }
  }


4、关键点总结

  • 无存储空间:SynchronousQueue 没有容量,插入和移除操作必须一一对应。

  • 阻塞行为:如果没有配对的插入或移除操作,线程会一直阻塞。

  • 公平性:公平模式下,等待时间最长的线程优先获得执行机会。


二、SynchronousQueue的类结构

先看一下SynchronousQueue类里面有哪些属性:

public class SynchronousQueue<E>
        extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    /**
     * 转接器(栈和队列的父类)
     */
    abstract static class Transferer<E> {

        /**
         * 转移(put和take都用这一个方法)
         *
         * @param e     元素
         * @param timed 是否超时
         * @param nanos 纳秒
         */
        abstract E transfer(E e, boolean timed, long nanos);

    }

    /**
     * 栈实现类
     */
    static final class TransferStack<E> extends Transferer<E> {
    }

    /**
     * 队列实现类
     */
    static final class TransferQueue<E> extends Transferer<E> {
    }

}

SynchronousQueue底层是基于Transferer抽象类实现的,放数据和取数据的逻辑都耦合在transfer()方法中。而Transferer抽象类又有两个实现类,分别是基于栈结构实现和基于队列实现


1、初始化

SynchronousQueue常用的初始化方法有两个:

  • 1、无参构造方法

  • 2、指定容量大小的有参构造方法

       /**
        * 无参构造方法
        */
       BlockingQueue<Integer> blockingQueue1 = new SynchronousQueue<>();
    
       /**
        * 有参构造方法,指定是否使用公平锁(默认使用非公平锁)
        */
       BlockingQueue<Integer> blockingQueue2 = new SynchronousQueue<>(true);
    

再看一下对应的源码实现:

  /**
   * 无参构造方法
   */
  public SynchronousQueue() {
      this(false);
  }

  /**
   * 有参构造方法,指定是否使用公平锁
   */
  public SynchronousQueue(boolean fair) {
      transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
  }

可以看出SynchronousQueue的无参构造方法默认使用的非公平策略,有参构造方法可以指定使用公平策略。 操作策略:

  • 1、公平策略,基于队列实现的是公平策略,先进先出。

  • 2、非公平策略,基于栈实现的是非公平策略,先进后出。


2、栈的类结构

/**
 * 栈实现
 */
static final class TransferStack<E> extends Transferer<E> {

    /**
     * 头节点(也是栈顶节点)
     */
    volatile SNode head;

    /**
     * 栈节点类
     */
    static final class SNode {

        /**
         * 当前操作的线程
         */
        volatile Thread waiter;

        /**
         * 节点值(取数据的时候,该字段为null)
         */
        Object item;

        /**
         * 节点模式(也叫操作类型)
         */
        int mode;

        /**
         * 后继节点
         */
        volatile SNode next;

        /**
         * 匹配到的节点
         */
        volatile SNode match;

    }
}

节点模式有以下三种:


3、栈的transfer方法实现

transfer()方法中,把放数据和取数据的逻辑耦合在一块了,逻辑有点绕,不过核心逻辑就四点,把握住就能豁然开朗。其实就是从栈顶压入,从栈顶弹出。


详细流程如下:


1、首先判断当前线程的操作类型与栈顶节点的操作类型是否一致,比如都是放数据,或者都是取数据。


2、如果是一致,把当前操作包装成SNode节点,压入栈顶,并挂起当前线程。


3、如果不一致,表示相互匹配(比如当前操作是放数据,而栈顶节点是取数据,或者相反)。然后也把当前操作包装成SNode节点压入栈顶,并使用tryMatch()方法匹配两个节点,匹配成功后,弹出两个这两个节点,并唤醒栈顶节点线程,同时把数据传递给栈顶节点线程,最后返回。


4、栈顶节点线程被唤醒,继续执行,然后返回传递过来的数据。

  /**
   * 转移(put和take都用这一个方法)
   *
   * @param e     元素(取数据的时候,元素为null)
   * @param timed 是否超时
   * @param nanos 纳秒
   */
  E transfer(E e, boolean timed, long nanos) {
      SNode s = null;
      // 1. e为null,表示要取数据,否则是放数据
      int mode = (e == null) ? REQUEST : DATA;
      for (; ; ) {
          SNode h = head;
          // 2. 如果本次操作跟栈顶节点模式相同(都是取数据,或者都是放数据),就把本次操作包装成SNode,压入栈顶
          if (h == null || h.mode == mode) {
              if (timed && nanos <= 0) {
                  if (h != null && h.isCancelled()) {
                      casHead(h, h.next);
                  } else {
                      return null;
                  }
                  // 3. 把本次操作包装成SNode,压入栈顶,并挂起当前线程
              } else if (casHead(h, s = snode(s, e, h, mode))) {
                  // 4. 挂起当前线程
                  SNode m = awaitFulfill(s, timed, nanos);
                  if (m == s) {
                      clean(s);
                      return null;
                  }
                  // 5. 当前线程被唤醒后,如果栈顶有了新节点,就删除当前节点
                  if ((h = head) != null && h.next == s) {
                      casHead(h, s.next);
                  }
                  return (E) ((mode == REQUEST) ? m.item : s.item);
              }
              // 6. 如果栈顶节点类型跟本次操作不同,并且模式不是FULFILLING类型
          } else if (!isFulfilling(h.mode)) {
              if (h.isCancelled()) {
                  casHead(h, h.next);
              }
              // 7. 把本次操作包装成SNode(类型是FULFILLING),压入栈顶
              else if (casHead(h, s = snode(s, e, h, FULFILLING | mode))) {
                  // 8. 使用死循环,直到匹配到对应的节点
                  for (; ; ) {
                      // 9. 遍历下个节点
                      SNode m = s.next;
                      // 10. 如果节点是null,表示遍历到末尾,设置栈顶节点是null,结束。
                      if (m == null) {
                          casHead(s, null);
                          s = null;
                          break;
                      }
                      SNode mn = m.next;
                      // 11. 如果栈顶的后继节点跟栈顶节点匹配成功,就删除这两个节点,结束。
                      if (m.tryMatch(s)) {
                          casHead(s, mn);
                          return (E) ((mode == REQUEST) ? m.item : s.item);
                      } else {
                          // 12. 如果没有匹配成功,就删除栈顶的后继节点,继续匹配
                          s.casNext(m, mn);
                      }
                  }
              }
          } else {
              // 13. 如果栈顶节点类型跟本次操作不同,并且是FULFILLING类型,
              // 就再执行一遍上面第8步for循环中的逻辑(很少概率出现)
              SNode m = h.next;
              if (m == null) {
                  casHead(h, null);
              } else {
                  SNode mn = m.next;
                  if (m.tryMatch(h)) {
                      casHead(h, mn);
                  } else {
                      h.casNext(m, mn);
                  }
              }
          }
      }
  }

不用关心细枝末节,把握住代码核心逻辑即可。 再看一下第4步,挂起线程的代码逻辑: 核心逻辑就两条:

  • 第6步,挂起当前线程

  • 第3步,当前线程被唤醒后,直接返回传递过来的match节点

     /**
      * 等待执行
      *
      * @param s     节点
      * @param timed 是否超时
      * @param nanos 超时时间
      */
     SNode awaitFulfill(SNode s, boolean timed, long nanos) {
         // 1. 计算超时时间
         final long deadline = timed ? System.nanoTime() + nanos : 0L;
         Thread w = Thread.currentThread();
         // 2. 计算自旋次数
         int spins = (shouldSpin(s) ?
                 (timed ? maxTimedSpins : maxUntimedSpins) : 0);
         for (; ; ) {
             if (w.isInterrupted())
                 s.tryCancel();
             // 3. 如果已经匹配到其他节点,直接返回
             SNode m = s.match;
             if (m != null)
                 return m;
             if (timed) {
                 // 4. 超时时间递减
                 nanos = deadline - System.nanoTime();
                 if (nanos <= 0L) {
                     s.tryCancel();
                     continue;
                 }
             }
             // 5. 自旋次数减一
             if (spins > 0)
                 spins = shouldSpin(s) ? (spins - 1) : 0;
             else if (s.waiter == null)
                 s.waiter = w;
                 // 6. 开始挂起当前线程
             else if (!timed)
                 LockSupport.park(this);
             else if (nanos > spinForTimeoutThreshold)
                 LockSupport.parkNanos(this, nanos);
         }
     }
    

再看一下匹配节点的tryMatch()方法逻辑: 作用就是唤醒栈顶节点,并当前节点传递给栈顶节点。

  /**
   * 匹配节点
   *
   * @param s 当前节点
   */
  boolean tryMatch(SNode s) {
      if (match == null &&
              UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
          Thread w = waiter;
          if (w != null) {
              waiter = null;
              // 1. 唤醒栈顶节点
              LockSupport.unpark(w);
          }
          return true;
      }
      // 2. 把当前节点传递给栈顶节点
      return match == s;
  }


4、队列的类结构

  /**
   * 队列实现
   */
  static final class TransferQueue<E> extends Transferer<E> {

      /**
       * 头节点
       */
      transient volatile QNode head;

      /**
       * 尾节点
       */
      transient volatile QNode tail;

      /**
       * 队列节点类
       */
      static final class QNode {

          /**
           * 当前操作的线程
           */
          volatile Thread waiter;

          /**
           * 节点值
           */
          volatile Object item;

          /**
           * 后继节点
           */
          volatile QNode next;

          /**
           * 当前节点是否为数据节点
           */
          final boolean isData;
      }
  }

可以看出TransferQueue队列是使用带有头尾节点的单链表实现的。 还有一点需要提一下,TransferQueue默认构造方法,会初始化头尾节点,默认是空节点。

/**
 * TransferQueue默认的构造方法
 */
TransferQueue() {
    QNode h = new QNode(null, false);
    head = h;
    tail = h;
}


队列的transfer方法实现


队列使用的公平策略,体现在,每次操作的时候,都是从队尾压入,从队头弹出。 详细流程如下:


1、首先判断当前线程的操作类型与队尾节点的操作类型是否一致,比如都是放数据,或者都是取数据。


2、如果是一致,把当前操作包装成QNode节点,压入队尾,并挂起当前线程。


3、如果不一致,表示相互匹配(比如当前操作是放数据,而队尾节点是取数据,或者相反)。然后在队头节点开始遍历,找到与当前操作类型相匹配的节点,把当前操作的节点值传递给这个节点,并弹出这个节点,唤醒这个节点的线程,最后返回。


4、队头节点线程被唤醒,继续执行,然后返回传递过来的数据。

    /**
     * 转移(put和take都用这一个方法)
     *
     * @param e     元素(取数据的时候,元素为null)
     * @param timed 是否超时
     * @param nanos 超时时间
     */
    E transfer(E e, boolean timed, long nanos) {
        QNode s = null;
        // 1. e不为null,表示要放数据,否则是取数据
        boolean isData = (e != null);
        for (; ; ) {
            QNode t = tail;
            QNode h = head;
            if (t == null || h == null) {
                continue;
            }

            // 2. 如果本次操作跟队尾节点模式相同(都是取数据,或者都是放数据),就把本次操作包装成QNode,压入队尾
            if (h == t || t.isData == isData) {
                QNode tn = t.next;
                if (t != tail) {
                    continue;
                }
                if (tn != null) {
                    advanceTail(t, tn);
                    continue;
                }
                if (timed && nanos <= 0) {
                    return null;
                }
                // 3. 把本次操作包装成QNode,压入队尾
                if (s == null) {
                    s = new QNode(e, isData);
                }
                if (!t.casNext(null, s)) {
                    continue;
                }
                advanceTail(t, s);
                // 4. 挂起当前线程
                Object x = awaitFulfill(s, e, timed, nanos);
                // 5. 当前线程被唤醒后,返回返回传递过来的节点值
                if (x == s) {
                    clean(t, s);
                    return null;
                }
                if (!s.isOffList()) {
                    advanceHead(t, s);
                    if (x != null) {
                        s.item = s;
                    }
                    s.waiter = null;
                }
                return (x != null) ? (E) x : e;
            } else {
                // 6. 如果本次操作跟队尾节点模式不同,就从队头结点开始遍历,找到模式相匹配的节点
                QNode m = h.next;
                if (t != tail || m == null || h != head) {
                    continue;
                }

                Object x = m.item;
                // 7. 把当前节点值e传递给匹配到的节点m
                if (isData == (x != null) || x == m ||
                        !m.casItem(x, e)) {
                    advanceHead(h, m);
                    continue;
                }
                // 8. 弹出队头节点,并唤醒节点m
                advanceHead(h, m);
                LockSupport.unpark(m.waiter);
                return (x != null) ? (E) x : e;
            }
        }
    }
posted @ 2025-02-20 00:29  jock_javaEE  阅读(39)  评论(0)    收藏  举报