Exchanger

此类提供对外的操作是同步的;
用于成对出现的线程之间交换数据【主场景】;
可以视作双向的同步队列;
可应用于基因算法、流水线设计、数据校对等场景

创建实例

    /**
* arena 数组中两个已使用的 slot 之间的索引距离,将它们分开以避免错误的共享
*/
private static final int ASHIFT = 5; /**
* arena 数组的最大索引值,最大 size 值为 0xff+1
*/
private static final int MMASK = 0xff; /**
* Unit for sequence/version bits of bound field. Each successful
* change to the bound also adds SEQ.
*/
private static final int SEQ = MMASK + 1; /** JVM 的 CPU 核数,用于自旋和扩容控制 */
private static final int NCPU = Runtime.getRuntime().availableProcessors(); /**
* arena 的最大索引值:原则上可以让所有线程不发生竞争
*/
static final int FULL = NCPU >= MMASK << 1 ? MMASK : NCPU >>> 1; /**
* 当前线程阻塞等待匹配节点前的自旋次数,CPU==1 时不进行自旋
*/
private static final int SPINS = 1 << 10; /**
* Value representing null arguments/returns from public methods.
* 旧 API 不支持 null 值所以需要适配。
*/
private static final Object NULL_ITEM = new Object(); /**
* 交换超时的返回值对象
*/
private static final Object TIMED_OUT = new Object(); @jdk.internal.vm.annotation.Contended static final class Node {
// Arena index
int index;
// Last recorded value of Exchanger.bound
int bound;
// Number of CAS failures at current bound
int collides;
// 自旋的伪随机数
int hash;
// 线程的当前数据对象
Object item;
// 匹配线程的数据对象
volatile Object match;
// 驻留阻塞线程
volatile Thread parked;
} /** 参与者 */
static final class Participant extends ThreadLocal<Node> {
@Override
public Node initialValue() { return new Node(); }
} /**
* 每个线程的状态
*/
private final Participant participant; /**
* 消除数组,只在出现竞争时初始化。
*/
private volatile Node[] arena; /**
* 未发生竞争时使用的 slot
*/
private volatile Node slot; /**
* The index of the largest valid arena position, OR'ed with SEQ
* number in high bits, incremented on each update. The initial
* update from 0 to SEQ is used to ensure that the arena array is
* constructed only once.
*/
private volatile int bound; /**
* Creates a new Exchanger.
*/
public Exchanger() {
participant = new Participant();
}

线程间交换数据

    /**
* 阻塞等待其他线程到达交换点后执行数据交换,支持中断
*/
@SuppressWarnings("unchecked")
public V exchange(V x) throws InterruptedException {
Object v;
// 将目标对象 v 进行编码
final Object item = x == null ? NULL_ITEM : x; // translate null args
/**
* 1)arena==null,表示未出现线程竞争,则使用 slot 进行数据交换
* 2)线程已经中断,则抛出 InterruptedException
* 3)arena!=null,则使用 arena 中的 slot 进行数据交换
*/
if ((arena != null ||
(v = slotExchange(item, false, 0L)) == null) &&
(Thread.interrupted() || // disambiguates null return
(v = arenaExchange(item, false, 0L)) == null)) {
throw new InterruptedException();
}
// 解码目标对象
return v == NULL_ITEM ? null : (V)v;
} /**
* 未出现竞争时的数据交换方式
* @param item 需要交换的目标对象
* @param timed 是否是超时模式
* @param ns 超时的纳秒数
* @return
* 1)目标线程的数据对象
* 2)null slot 交换出现竞争、线程被中断
* 3)TIMED_OUT 交换超时
*/
private final Object slotExchange(Object item, boolean timed, long ns) {
// 读取参与者节点
final Node p = participant.get();
// 读取当前线程
final Thread t = Thread.currentThread();
// 线程被设置了中断标识,则返回 null
if (t.isInterrupted()) {
return null;
} for (Node q;;) {
// 1)已经有线程在阻塞等待交换数据
if ((q = slot) != null) {
// 将 slot 置为 null
if (SLOT.compareAndSet(this, q, null)) {
// 读取目标对象
final Object v = q.item;
// 写入交换对象
q.match = item;
// 如果线程在阻塞等待
final Thread w = q.parked;
if (w != null) {
// 则唤醒交换线程
LockSupport.unpark(w);
}
// 返回交换到的对象
return v;
}
/**
* NCPU > 1 多核 CPU 才会启用竞技场 &&
* 设置最大有效的 arena 索引值
*/
if (NCPU > 1 && bound == 0 &&
BOUND.compareAndSet(this, 0, SEQ)) {
// 创建竞技场
arena = new Node[FULL + 2 << ASHIFT];
}
}
// 2)启用了 arena
else if (arena != null) {
return null; // caller must reroute to arenaExchange
// 3)slot 为空 && 未启用 arena
} else {
// 写入交换数据
p.item = item;
// 将 Node 写入 slot,成功则退出循环
if (SLOT.compareAndSet(this, null, p)) {
break;
}
// 出现竞争,则重试
p.item = null;
}
} // 等待释放
int h = p.hash;
// 计算截止时间
final long end = timed ? System.nanoTime() + ns : 0L;
// 计算自旋次数,多核 CPU 为 1024
int spins = NCPU > 1 ? SPINS : 1;
Object v;
// 只要没有匹配的交换数据
while ((v = p.match) == null) {
// 1)自旋还未完成
if (spins > 0) {
h ^= h << 1; h ^= h >>> 3; h ^= h << 10;
if (h == 0) {
h = SPINS | (int)t.getId();
} else if (h < 0 && (--spins & (SPINS >>> 1) - 1) == 0) {
Thread.yield();
}
}
// 2)slot 已经更新
else if (slot != p) {
spins = SPINS;
/**
* 3)线程未中断 && 未启用竞技场 && 不是超时模式;
* 如果是超时模式,则计算剩余时间,当前还未超时
*/
} else if (!t.isInterrupted() && arena == null &&
(!timed || (ns = end - System.nanoTime()) > 0L)) {
// 写入驻留线程
p.parked = t;
// 如果 slot 未更新,没有线程来进行数据交换
if (slot == p) {
// 1)阻塞等待
if (ns == 0L) {
LockSupport.park(this);
// 2)超时阻塞等待
} else {
LockSupport.parkNanos(this, ns);
}
}
// 线程释放后,清空 parked
p.parked = null;
}
// 如果线程被中断或已经超时,则将 slot 清空
else if (SLOT.compareAndSet(this, p, null)) {
// 如果是超时,则返回 TIMED_OUT;线程中断,则返回 null
v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
break;
}
}
// 清空 match
MATCH.setRelease(p, null);
// 清空 item
p.item = null;
p.hash = h;
// 返回交换到的数据对象
return v;
} /**
* Exchange function when arenas enabled. See above for explanation.
*
* @param item 需要交换的目标对象
* @param timed 是否是超时模式
* @param ns 超时的纳秒数
* @return
* 1)目标线程的数据对象
* 2)null 线程被中断
* 3)TIMED_OUT 交换超时
*/
private final Object arenaExchange(Object item, boolean timed, long ns) {
// 读取 arena
final Node[] a = arena;
// 读取数组长度
final int alen = a.length;
// 读取当前线程的参与者,初始值为 0
final Node p = participant.get();
for (int i = p.index;;) { // access slot at i
int b, m, c;
// 一般为 31
int j = (i << ASHIFT) + (1 << ASHIFT) - 1;
if (j < 0 || j >= alen) {
j = alen - 1;
}
// 读取指定 slot 的 Node
final Node q = (Node) AA.getAcquire(a, j);
// 1)目标 slot 已经有线程在等待交换数据,则尝试清空 slot
if (q != null && AA.compareAndSet(a, j, q, null)) {
// 读取目标对象
final Object v = q.item; // release
// 写入交换对象
q.match = item;
final Thread w = q.parked;
if (w != null) {
// 唤醒驻留线程
LockSupport.unpark(w);
}
// 返回交换到的值
return v;
// 2)目标索引 i 在有效索引范围内 && slot 为 null
} else if (i <= (m = (b = bound) & MMASK) && q == null) {
// 写入 item
p.item = item; // offer
// 写入节点
if (AA.compareAndSet(a, j, null, p)) {
// 计算截止时间
final long end = timed && m == 0 ? System.nanoTime() + ns : 0L;
// 读取当前线程
final Thread t = Thread.currentThread(); // wait
// 读取自旋次数 1024
for (int h = p.hash, spins = SPINS;;) {
// 读取匹配数据
final Object v = p.match;
// 1)已经有线程将交换数据写入
if (v != null) {
MATCH.setRelease(p, null);
p.item = null; // clear for next use
p.hash = h;
return v;
// 2)自旋还未结束
} else if (spins > 0) {
h ^= h << 1;
h ^= h >>> 3;
h ^= h << 10; // xorshift
if (h == 0) {
h = SPINS | (int) t.getId();
} else if (h < 0 && // approx 50% true
(--spins & (SPINS >>> 1) - 1) == 0) {
Thread.yield(); // two yields per wait
}
// 3)slot 已经更新
} else if (AA.getAcquire(a, j) != p) {
spins = SPINS; // releaser hasn't set match yet
// 4) 线程未中断、未超时
} else if (!t.isInterrupted() && m == 0 && (!timed || (ns = end - System.nanoTime()) > 0L)) {
// 写入驻留线程
p.parked = t; // minimize window
// 如果 slot 未更新,则线程被阻塞
if (AA.getAcquire(a, j) == p) {
if (ns == 0L) {
LockSupport.park(this);
} else {
LockSupport.parkNanos(this, ns);
}
}
p.parked = null;
// 5)slot 未更新 && 线程超时或中断,则清空 slot
} else if (AA.getAcquire(a, j) == p && AA.compareAndSet(a, j, p, null)) {
if (m != 0) {
BOUND.compareAndSet(this, b, b + SEQ - 1);
}
p.item = null;
p.hash = h;
i = p.index >>>= 1; // descend
// 线程被中断
if (Thread.interrupted()) {
return null;
}
// 线程超时
if (timed && m == 0 && ns <= 0L) {
return TIMED_OUT;
}
break; // expired; restart
}
}
// 2)写入 slot 出现竞争
} else {
p.item = null; // clear offer
}
} else {
if (p.bound != b) { // stale; reset
p.bound = b;
p.collides = 0;
i = i != m || m == 0 ? m : m - 1;
} else if ((c = p.collides) < m || m == FULL || !BOUND.compareAndSet(this, b, b + SEQ + 1)) {
p.collides = c + 1;
i = i == 0 ? m : i - 1; // cyclically traverse
} else {
i = m + 1; // grow
}
p.index = i;
}
}
}

Exchanger 源码分析的更多相关文章

  1. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  2. 多线程高并发编程(6) -- Semaphere、Exchanger源码分析

    一.Semaphere 1.概念 一个计数信号量.在概念上,信号量维持一组许可证.如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它.每个release()添加许可证,潜在地释 ...

  3. 源码分析:Exchanger之数据交换器

    简介 Exchanger是Java5 开始引入的一个类,它允许两个线程之间交换持有的数据.当Exchanger在一个线程中调用exchange方法之后,会阻塞等待另一个线程调用同样的exchange方 ...

  4. Dubbo 源码分析 - 服务引用

    1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...

  5. Dubbo 源码分析 - 服务导出

    1.服务导出过程 本篇文章,我们来研究一下 Dubbo 导出服务的过程.Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可 ...

  6. Dubbo2.7源码分析-如何发布服务

    Dubbo的服务发布逻辑是比较复杂的,我还是以Dubbo自带的示例讲解,这样更方便和容易理解. Provider配置如下: <?xml version="1.0" encod ...

  7. JDK源码分析之concurrent包(一) -- Executor架构

    Java5新出的concurrent包中的API,是一些并发编程中实用的的工具类.在高并发场景下的使用非常广泛.笔者在这做了一个针对concurrent包中部分常用类的源码分析系列.本系列针对的读者是 ...

  8. 🏆【Alibaba微服务技术系列】「Dubbo3.0技术专题」回顾Dubbo2.x的技术原理和功能实现及源码分析(温故而知新)

    RPC服务 什么叫RPC? RPC[Remote Procedure Call]是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范.它允许程序调用另一个地址空间(通常是共享网络的另 ...

  9. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

随机推荐

  1. CF和OF的区别

    进位标志CF和溢出标志OF的区别: 有符号数和无符号数只是认为的进行区分,计算机从来不区分有符号数和无符号数.对于运算的数来说,只要符合进位的情况,CF就置1.只要符合溢出的情况,OF就置1.但是后续 ...

  2. 修改jar包中class文件

    某日,想要更改jar包中的某个class文件,有无rar无法解压jar文件,故找到如下方式进行操作 1.解压某个jar包:在需要解压的jar包目录下,打开命令行(cmd),输入如下命令,输入:C:\j ...

  3. CSUST 8.5 早训

    ## Problem A A - Meeting of Old Friends CodeForces - 714A 题意: 解题说明:此题其实是求两段区间的交集,注意要去除掉交集中的某个点. 题解: ...

  4. jQuery进阶第四天(2019 10.13)

    1 初识面向对象(面向对象是一种思维方式) 以前写的代码 var name = '莉莉'; var sex = '女'; var age = 18; var name1 = '小明'; var sex ...

  5. 关于tomcat NoClassDefDoundErr异常的记录

    在做DRP项目的时候,copy了drp1.3,粘贴重命名成drp1.4,把drp1.4加入到tomcat中,发现drp1.4中新加的jsp可以正常运行,而从1.3那copy来的不能运行,抛出NoCla ...

  6. FPGA异步时钟系统中信号处理之单比特信号

    有些东西当你习以为常而不去深思熟虑的时候,致命的错误就会因此埋下!      FPGA开发中难免会遇到跨时钟域处理的问题,而对于单比特信号,我会不假思索的回答:打两拍不久解决了吗?但是事实时,这佯作的 ...

  7. GUI学习之二十八—QMessageBox

    今天来学习下QMessageBox. QMessageBox主要用来通知用户或者请求用户提问和接收应答一个模态对话框. 一.对话框的构成 图标是有标准图标的,可以直接调用. 我们声明的消息框,初始状态 ...

  8. Python核心技术与实战——四|Python黑箱:输入与输出

    抽象的看,Python程序可以被看成一个黑箱:通过输入流将数据送达,经过处理后在输入,也就是说具备了一个图灵机运作的必要条件. 输入输出基础 最简单的输入是来自键盘的操作 name = input(' ...

  9. linux下vim如何清空一个文件?

    这是一个很巧妙的方法.如何来清空一个文件里的内容呢! 很简单,但确很实用: echo " " > filename(文件名称); 一句话就可以搞定.

  10. Maven项目的一些依赖

    Maven构建的Spring项目需要哪些依赖? <!-- Spring依赖 --> <!-- 1.Spring核心依赖 --> <dependency> <g ...