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. Django @csrf_exempt不能在类视图中工作(Django @csrf_exempt not working in class View)

    我在Django 1.9中有一个使用SessionMiddleware的应用程序.我想在同一个项目中为这个应用程序创建一个API,但是在做一个POST请求时,它不能使用@csrf_exempt注释. ...

  2. python中虚拟环境virtualenvwrapper的安装和使用

    虚拟环境为什么需要虚拟环境:       到目前为止,我们所有的第三方包安装都是直接通过 pip install xx 的方式进行安装的,这样安装会将那个包安装到你的系统级的 Python 环境中.但 ...

  3. Paper Reading_Database

    最近(以及预感接下来的一年)会读很多很多的paper......不如开个帖子记录一下读paper心得 AI+DB A. Pavlo et al., Self-Driving Database Engi ...

  4. Self-Driving Database

    最近一直在做 ML in Database 相关的工作.偶然发现CMU 19spring的15-721课程竟然专门安排了这个专题,不禁欣喜若狂,赶紧去学习了一下. Andy提出了self-drivin ...

  5. svn版本服务器的搭建和简单使用

    ⼀ 服务器搭建篇 1 在”应⽤用程序”⽂文件夹下,找到”实⽤用⼯工具”,打开”终端”APP 2 运⾏行svnadmin create repository,运⾏行完毕之后,可以在当前⺫⽬目录下找 到⼀ ...

  6. Django模板层2

    一.单表操作 1.1 开启test from django.test import TestCase import os # Create your tests here. if __name__ = ...

  7. Linux 设置定时清除buff/cache的脚本

    Linux 设置定时清除buff/cache的脚本 查看内存缓存状态 [root@heyong ~]# free -m total used free shared buff/cache availa ...

  8. TreeView详细用法

    Treeview用于显示按照树形结构进行组织的数据.          Treeview控件中一个树形图由节点(TreeNode)和连接线组成.TtreeNode是TTreeview的基本组成单元. ...

  9. 2019 计蒜之道 初赛 第一场 商汤AI园区的n个路口(中等) (树形dp)

    北京市商汤科技开发有限公司建立了新的 AI 人工智能产业园,这个产业园区里有 nn 个路口,由 n - 1n−1 条道路连通.第 ii 条道路连接路口 u_iui​ 和 v_ivi​. 每个路口都布有 ...

  10. hdu 1087 最大递增子序列和

    #include <bits/stdc++.h> #define PI acos(-1.0) #define mem(a,b) memset((a),b,sizeof(a)) #defin ...