并发队列之无容量阻塞队列SynchronousQueue

SynchronousQueue是一个特殊的无容量阻塞队列,其特点是读写线程需要同步,读操作需要等待写操作,反之亦然。它在Java的ThreadPoolExecutor中有所应用,提供公平和非公平两种策略。公平策略遵循FIFO原则,非公平策略可能导致先入者后匹配。队列内部使用单链表实现,非公平策略通过栈结构,公平策略通过队列结构。
摘要由CSDN通过智能技术生成

1. SynchronousQueue 简述

它是一个特殊的队列,它的名字其实就蕴含了它的特征 – - 同步的队列。为什么说是同步的呢?这里说的并不是多线程的并发问题,而是 因为当一个线程往队列中写入一个元素时,写入操作不会立即返回,需要等待另一个线程来将这个元素拿走;同理,当一个读线程做读操作的时候,同样需要一个相匹配的写线程的写操作。这里的 Synchronous 指的就是读线程和写线程需要同步,一个读线程匹配一个写线程

我们很少使用到 SynchronousQueue 这个类,不过它在线程池的实现类 ScheduledThreadPoolExecutor 中得到了应用

  • SynchronousQueue 来自于 jdk 1.5JUC 包,是一个 线程安全的阻塞队列
  • SynchronousQueue 不能简单的使用有界或无界来形容,因为它的内部根本就没有容量
  • SynchronousQueue 中每个插入操作必须等待另一个线程的对应移除操作,反之亦然;两个操作是同步等待的,即一个先到达的操作必须等待另一个匹配的操作出现,两个操作才能成功匹配并传递数据之后返回
  • SynchronousQueue 支持公平策略和非公平策略(默认),所以 底层有两种数据结构:队列(实现公平策略,先进先出,单链表实现)和 (实现非公平策略,先进后出,单链表实现)

在这里插入图片描述

2. SynchronousQueue 源码

2.1. SynchronousQueue 属性

/**
 * CPU中通常一个内核一个线程,后来有了超线程技术,可以把一个物理核心,模拟成两个逻辑核心,线程量增加一倍
 * 因此这里获取的是CPU的实际可用线程数量,比如 i7-8750H 它具有6核心12线程,因此获取的就是12而不是6
 * 通常CPU的实际可用线程数量越高,运行并发的程序的效率也越高
 */
static final int NCPUS = Runtime.getRuntime().availableProcessors();

/**
 * 超时等待的线程在阻塞之前应该自旋的次数
 * 该值是经验推导的 ——它适用于各种处理器和 OS。从经验上看,最佳值似乎不会随 CPU 数量(超过 2)而变化,因此只是一个常数(0或32)。
 */
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;

/**
 * 非超时等待的线程在阻塞之前应该自旋的次数
 * 通常大于超时等待的线程的旋转次数,因为不需要检查每次旋转的之后的剩余超时时间,它们旋转的更快(0或512)
 */
static final int maxUntimedSpins = maxTimedSpins * 16;

/**
 * 采用自旋而不是使用park阻塞的超时时间边界纳秒数,这是也是一个估计值
 * 即超时时间大于1000L,那么使用parkNanos阻塞当前线程,否则采用快速的自旋等待即可
 * 原因在于,非常短的超时等待无法做到十分精确,如果这时再进行超时等待,相反会让nanosTimeout的超时从整体上表现得反而不精确
 * 因此,在超时非常短的场景下,AQS会进入无条件的快速自旋而不是挂起线程
 * <p>
 * 这个参数在AQS的超时获取锁,Condition的超时等待中也被使用
 */
static final long spinForTimeoutThreshold = 1000L;

/**
 * Transferer 实例引用,用于在传输操作无法正常完成时存储阻塞的线程以及元素
 * 只会初始化某一个公平或者非公平模式的子类实例
 */
private transient volatile Transferer<E> transferer;

/**
 * 下面的字段只是为了兼容JDK1.5的SynchronousQueue的序列化策略
 * 只有在序列化或者反序列化时才会初始化,在高版本的SynchronousQueue中永远不会使用
 */
private ReentrantLock qlock;
private WaitQueue waitingProducers;
private WaitQueue waitingConsumers;

2.2. SynchronousQueue 内部类

在这里插入图片描述

2.2.1. TransferStack 类(非公平策略,单链表构成栈)

  • 底层是一个单链表的结构,结点类型是 SNode 类型
  • TransferStack 持有一个 单链表头部 head 作为栈顶
  • 每一个入栈的线程遵循先进后出的原则
  • 属性的赋值都是采用 CAS 操作来完成的
abstract static class Transferer<E> {
   
	abstract E transfer(E e, boolean timed, long nanos);
}

static final class TransferStack<E> extends Transferer<E> {
   

    // 表示消费数据的出栈线程
    static final int REQUEST = 0;
    
    // 表示生产数据的入栈线程
    static final int DATA = 1;
    
    // 表示匹配另一个入栈线程或出栈线程
    static final int FULFILLING = 2;
    
    // 栈的头部元素,即栈顶
    volatile SNode head;


    // TransferStack的数据结构
    static final class SNode {
   
     
        volatile SNode next;
               
        // 相匹配的节点
        volatile SNode match;       
        
        // 等待的线程
        volatile Thread waiter;     

        Object item;         
              
        // 模式: REQUEST 或者 DATA 或者 FULFILLING
        int mode;

        SNode(Object item) {
   
            this.item = item;
        }

        // CAS操作都是使用 UNSAFE来完成的
        private static final sun.misc.Unsafe UNSAFE;
        private static final long matchOffset;
        private static final long nextOffset;

        static {
   
            try {
   
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> k = SNode.class;
                matchOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("match"));
                nextOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("next"));
            } catch (Exception e) {
   
                throw new Error(e);
            }
        }
    }
    // ......
}

通过 SNodemode 属性可以确定三种模式的线程节点

  • REQUEST:表示出栈的线程在等待消费数据
  • DATA:表示入栈的线程在等待生产数据
  • FULFILLING:表示某个请求模式的结点正在与栈中的另一个等待的结点完成匹配

2.2.2. TransferQueue 类(公平策略,单链表构成队列)

  • 底层是一个单链表的结构,结点类型是 Qnode 类型
  • TransferQueue 持有一个单链表头部 head 和尾部 tail
  • 每一个入队的线程遵循先进先出的原则
abstract static class Transferer<E> {
   
	abstract E transfer(E e, boolean timed, long nanos);
}

static final class TransferQueue<E> extend
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值