目录
1. SynchronousQueue
简述
它是一个特殊的队列,它的名字其实就蕴含了它的特征 – - 同步的队列。为什么说是同步的呢?这里说的并不是多线程的并发问题,而是 因为当一个线程往队列中写入一个元素时,写入操作不会立即返回,需要等待另一个线程来将这个元素拿走;同理,当一个读线程做读操作的时候,同样需要一个相匹配的写线程的写操作。这里的 Synchronous
指的就是读线程和写线程需要同步,一个读线程匹配一个写线程
我们很少使用到 SynchronousQueue
这个类,不过它在线程池的实现类 ScheduledThreadPoolExecutor
中得到了应用
SynchronousQueue
来自于jdk 1.5
的JUC
包,是一个 线程安全的阻塞队列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);
}
}
}
// ......
}
通过 SNode
的 mode
属性可以确定三种模式的线程节点
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