JUC同步锁原理源码解析六----Exchanger

Exchanger

Exchanger的来源

A synchronization point at which threads can pair and swap elements within pairs.  Each thread presents some object on entry to the {@link #exchange exchange} method, matches with a partner thread, and receives its partner's object on return. An Exchanger may be viewed as a bidirectional form of a {@link SynchronousQueue}.

​ JDK中对Exchanger的定义是在一个同步线程点,配对的线程可以交换彼此的属性数据。每一个线程提交对象数据并调用exchange方法,匹配到一个线程并接受该线程携带的数据返回。Exchanger可以被当成一个双向的同步队列。当然Exchanger并不是说只有两个线程进行匹配,它也可以进行多对多的匹配,但是只有成对的线程可以匹配并交换数据成功。

Exchanger的底层实现

​ Exchanger的底层实现依旧依赖于CAS的自旋锁操作,通过cas保证原子性的操作

2.Exchanger

基本使用

public class ExchangerDemo {
static Exchanger<String> exchanger = new Exchanger<>();
public static void main(String[] args) {
new Thread(()->{
String s = "T1";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t1").start(); new Thread(()->{
String s = "T2";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s); }, "t2").start();
}
}

Exchanger类

public class Exchanger<V> {
/**
* The byte distance (as a shift value) between any two used slots
* in the arena. 1 << ASHIFT should be at least cacheline size.
*/
private static final int ASHIFT = 7; /**
* The maximum supported arena index. The maximum allocatable
* arena size is MMASK + 1. Must be a power of two minus one, less
* than (1<<(31-ASHIFT)). The cap of 255 (0xff) more than suffices
* for the expected scaling limits of the main algorithms.
*/
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; /** The number of CPUs, for sizing and spin control */
private static final int NCPU = Runtime.getRuntime().availableProcessors(); /**
* The maximum slot index of the arena: The number of slots that
* can in principle hold all threads without contention, or at
* most the maximum indexable value.
*/
static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1; /**
* The bound for spins while waiting for a match. The actual
* number of iterations will on average be about twice this value
* due to randomization. Note: Spinning is disabled when NCPU==1.
*/
private static final int SPINS = 1 << 10; /**
* Value representing null arguments/returns from public
* methods. Needed because the API originally didn't disallow null
* arguments, which it should have.
*/
private static final Object NULL_ITEM = new Object(); /**
* Sentinel value returned by internal exchange methods upon
* timeout, to avoid need for separate timed versions of these
* methods.
*/
private static final Object TIMED_OUT = new Object(); /** The corresponding thread local class */
static final class Participant extends ThreadLocal<Node> {
public Node initialValue() { return new Node(); }
} /**
* Per-thread state
*/
private final Participant participant; /**
* Elimination array; null until enabled (within slotExchange).
* Element accesses use emulation of volatile gets and CAS.
*/
private volatile Node[] arena; /**
* Slot used until contention detected.
*/
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;
}

Node类

@sun.misc.Contended static final class Node {//@sun.misc.Contended 进行缓存行填充,防止数据移植刷新缓存行,造成性能损耗
int index; // Arena index
int bound; // Last recorded value of Exchanger.bound
int collides; // Number of CAS failures at current bound
int hash; // Pseudo-random for spins
Object item; // This thread's current item
volatile Object match; // Item provided by releasing thread
volatile Thread parked; // Set to this thread when parked, else null
}

Exchanger的构造器

public Exchanger() {
participant = new Participant();
}

exchange()方法

public V exchange(V x) throws InterruptedException {
Object v;
Object item = (x == null) ? NULL_ITEM : x; // 将对象赋值给item
if ((arena != null ||//表示有多个线程在竞争匹配
(v = slotExchange(item, false, 0L)) == null) &&//slot匹配的对象返回空,slot表示的一对一的匹配。
((Thread.interrupted() || // 线程是否发生中断
(v = arenaExchange(item, false, 0L)) == null)))//arena表示发生多线程竞争,如果匹配失败
throw new InterruptedException();//抛出中断异常
return (v == NULL_ITEM) ? null : (V)v;//返回匹配后并交换的数据
}

exchange(V x, long timeout, TimeUnit unit)方法:

public V exchange(V x, long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException {
Object v;
Object item = (x == null) ? NULL_ITEM : x;// 将对象赋值给item
long ns = unit.toNanos(timeout);//超时的时间换算成纳秒
if ((arena != null ||
(v = slotExchange(item, true, ns)) == null) &&//slot匹配的对象返回空,slot表示的一对一的匹配。
((Thread.interrupted() ||// 线程是否发生中断
(v = arenaExchange(item, true, ns)) == null)))//arena表示发生多线程竞争
throw new InterruptedException();//抛出中断异常
if (v == TIMED_OUT)//如果返回对象是超时
throw new TimeoutException();//抛出超时异常
return (v == NULL_ITEM) ? null : (V)v;//返回匹配后并交换的数据
}

slotExchange方法

private final Object slotExchange(Object item, boolean timed, long ns) {
Node p = participant.get();//获取参与者节点
Thread t = Thread.currentThread();//获取当前线程
if (t.isInterrupted()) // 判断是否发生中断
return null;//中断唤醒直接返回 for (Node q;;) {
if ((q = slot) != null) {//表示当前属于slot的匹配,也即不是多对多的情况
if (U.compareAndSwapObject(this, SLOT, q, null)) {//cas将当前slot置空
Object v = q.item;//获取节点q的item对象
q.match = item;//将item复制给节点的match,这里表示匹配成功
Thread w = q.parked;//获取当前线程阻塞在slot中等待的线程
if (w != null)//如果w线程不为空
U.unpark(w);//唤醒w线程
return v;//返回对象V
}
// create arena on contention, but continue until slot null
//走到这里表示竞争激烈,创建arena数组
if (NCPU > 1 && bound == 0 &&//如果CPU大于1并且bound为0表示bound前置判断,为0标识没创建arena
U.compareAndSwapInt(this, BOUND, 0, SEQ))//cas设置BOUND的值为序列号,同时保证多个线程的条件下,只有一个线程可以创建成功
arena = new Node[(FULL + 2) << ASHIFT];// FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1; MMASK = 0xff ; ASHIFT = 7 full按照CPU进行取值,如果CPU大于510,直接取MMASK,否则CPU数/2。 ASHIFT的值为7。所以2的7次方为128。也即可以保证缓存行对齐的大小
}
else if (arena != null)//走到这里表示竞争激烈,升级成多对多的匹配,所以直接返回调用arenaExchange的匹配
return null; // caller must reroute to arenaExchange
else {//这里表示当前线程找不到可以匹配的,所以将自身放到slot中等待后续匹配
p.item = item;//将item放到p.item中
if (U.compareAndSwapObject(this, SLOT, null, p))//将SLOT节点置为p节点
break;//退出
p.item = null;//cas失败将item置为空,继续循环
}
} // await release 走到这里的条件是,当前线程将自身cas放入到slot中成功后break退出上一个循环
int h = p.hash;//获取p节点的hash值
long end = timed ? System.nanoTime() + ns : 0L;//如果有超时时间,计算超时时间
int spins = (NCPU > 1) ? SPINS : 1;//SPINS = 1 << 10 默认自旋次数为1024。具体看CPU个数
Object v;
while ((v = p.match) == null) {//如果当前p.mach为空,表示没有匹配成功
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)//自旋到一定次数,让出CPU执行权。
Thread.yield();
}
else if (slot != p)//如果slot不等p继续自旋,因为竞争强烈,很快就可以匹配成功
spins = SPINS;
else if (!t.isInterrupted() && arena == null &&//判断线程是否中断并且arena是否为空
(!timed || (ns = end - System.nanoTime()) > 0L)) {//超时判断没有超时
U.putObject(t, BLOCKER, this);//将BLOCKER置为当前对象
p.parked = t;//parked为当前线程
if (slot == p)//slot等于p节点
U.park(false, ns);//阻塞等待
p.parked = null;//再次唤醒后,将p.parked的线程置为空
U.putObject(t, BLOCKER, null);//阻塞对象也置为空
}
else if (U.compareAndSwapObject(this, SLOT, p, null)) {//cas将slot的p节点置为空
v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;//如果已经超时并且不是中断唤醒,置为TIMED_OUT,否则为null
break;//退出循环
}
}
U.putOrderedObject(p, MATCH, null);//将p.macth置为空
p.item = null;//item也置为空
p.hash = h;//p.hash位置h
return v;//所以这里返回只有两种情况:1.slot中匹配成功 2.超时后返回null
}

arenaExchange方法

private final Object arenaExchange(Object item, boolean timed, long ns) {
Node[] a = arena;//获取到arena的数组
Node p = participant.get();//获取到节点p
for (int i = p.index;;) { //获取index的下标
int b, m, c; long j; // j is raw array offset
Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);//获取基地址+128缓存行对齐的节点Q
if (q != null && U.compareAndSwapObject(a, j, q, null)) {//如果q不为空并且cas将其职位空
Object v = q.item; //将q.item赋值给V
q.match = item;//将要交换的item值赋值为q.match
Thread w = q.parked;//获取到当前阻塞的线程
if (w != null)//如果线程不为空
U.unpark(w);//唤醒当前线程
return v;//返回匹配成功后交换过来的值
}
else if (i <= (m = (b = bound) & MMASK) && q == null) {//如果index小于最大的arena的下标,也即属于合法下标,并且当前q为空,q为空表示我自己是第一个进来的。将我自己放到arena的j下标处
p.item = item; //将当前item赋值为节点p
if (U.compareAndSwapObject(a, j, null, p)) {//cas将arena中下标为j的位置置为p节点
long end = (timed && m == 0) ? System.nanoTime() + ns : 0L;//计算超时时间
Thread t = Thread.currentThread(); // 获取当前线程
for (int h = p.hash, spins = SPINS;;) {//进行自旋
Object v = p.match;//获取当前p.match的值
if (v != null) {//如果匹配的值不为空,代表匹配成功了
U.putOrderedObject(p, MATCH, null);//将p节点的match值为空
p.item = null;//清空当前p.item的值
p.hash = h;//将h赋值为p
return v;返回匹配成功后交换过来的值
}
else if (spins > 0) {//匹配不成功,进行自旋等待
h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift
if (h == 0) // initialize hash
h = SPINS | (int)t.getId();
else if (h < 0 && // approx 50% true
(--spins & ((SPINS >>> 1) - 1)) == 0)//h<0并且自旋计算后未0,
Thread.yield(); //让出cpu执行权
}
else if (U.getObjectVolatile(a, j) != p)//获取当前arena中j下标的值不等于p
spins = SPINS; // 未匹配成功,赋值自旋次数
else if (!t.isInterrupted() && m == 0 &&//判断是否中断,并且arena
(!timed ||//是否超时等待
(ns = end - System.nanoTime()) > 0L)) {//超时时间是否大于0
U.putObject(t, BLOCKER, this); // 设置blocker阻塞对象为当前对象
p.parked = t;//parked的阻塞线程为当前线程
if (U.getObjectVolatile(a, j) == p)//判断当前arena的下标是否为p
U.park(false, ns);//阻塞等待
p.parked = null;//唤醒后将parked置为空
U.putObject(t, BLOCKER, null);//唤醒后将BLOCKER置为空
}
else if (U.getObjectVolatile(a, j) == p &&//如果当前arena的j下标处为p
U.compareAndSwapObject(a, j, p, null)) {//cas将j下标的p节点置为空
if (m != 0) //m表示最大的arena数组不等于0
U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1);//将数组缩小
p.item = null;//将item置为空
p.hash = h;//h赋值给p.hash
i = p.index >>>= 1;//将index下标右移1位
if (Thread.interrupted())//判断是否是中断唤醒
return null;//返回空
if (timed && m == 0 && ns <= 0L)//如果超时设置并且m为0,ns超时时间小于0表示已经超时
return TIMED_OUT;//返回TIMED_OUT
break; // expired; restart
}
}
}
else
p.item = null; //cas失败,将p.item还原
}
else {//进入这里的情况:q不为空,代表下标竞争激烈,cas失败。所以换个下标继续循环匹配
if (p.bound != b) { //如果p.bound不等于b,表示当前b已经修改过了
p.bound = b;//重新获取最新值
p.collides = 0;//将失败次数置为0;
i = (i != m || m == 0) ? m : m - 1;//这里直接取m是因为有线程可能已经往m中放入了待匹配节点
}
else if ((c = p.collides) < m || m == FULL ||//失败次数小于m,获取m==arena最大值。m为当前的bound的值。
!U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) {//cas将BOUND
p.collides = c + 1;//失败次数加1
i = (i == 0) ? m : i - 1; //若当前i为0,赋值m业绩最大下标,否则i的下标减1。如果是m==FULL条件进来,表示已经在最大下标处,所以从后往前查找
}
else
i = m + 1; //当cas设置bound成功后将i下标置为m+1,将cas的下标尽量打散。
p.index = i;//获取新的下标继续循环
}
}
}

4.留言

​ 到了这里,并发工具包常用的原子性工具类已经结束了,LockSupport由于直接调用底层的park方法,较为复杂,设计到JVM的源码,暂时能力有限,后续如果看懂了在进行更新

JUC同步锁原理源码解析六----Exchanger的更多相关文章

  1. 从ReentrantLock详解AQS原理源码解析

    数据结构 java.util.concurrent.locks.AbstractQueuedSynchronizer类中存在如下数据结构. // 链表结点 static final class Nod ...

  2. Spring Boot中@ConfigurationProperties注解实现原理源码解析

    0. 开源项目推荐 Pepper Metrics是我与同事开发的一个开源工具(https://github.com/zrbcool/pepper-metrics),其通过收集jedis/mybatis ...

  3. Spring注解Component原理源码解析

    在实际开发中,我们经常使用Spring的@Component.@Service.@Repository以及 @Controller等注解来实现bean托管给Spring容器管理.Spring是怎么样实 ...

  4. 【Spring实战】Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  5. vue双向绑定原理源码解析

    当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/maxlove123 ...

  6. 【转】【Spring实战】Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  7. 设计模式课程 设计模式精讲 8-8 单例设计模式-Enum枚举单例、原理源码解析以及反编译实战

    1 课堂解析 2 代码演练 2.1 枚举类单例解决序列化破坏demo 2.2 枚举类单例解决序列化破坏原理 2.3 枚举类单例解决反射攻击demo 2.4 枚举类单例解决反射攻击原理 3 jad的使用 ...

  8. java基础(十八)----- java动态代理原理源码解析

    关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 静态代理 1.静态代理 静态代理:由程序员创建或特定工 ...

  9. ReentrantLock(重入锁)的源码解析

    转自:从源码角度彻底理解ReentrantLock(重入锁)](https://www.cnblogs.com/takumicx/p/9402021.html)) 公平锁内部是FairSync,非公平 ...

  10. Celery 源码解析六:Events 的实现

    在 Celery 中,除了远程控制之外,还有一个元素可以让我们对分布式中的任务的状态有所掌控,而且从实际意义上来说,这个元素对 Celery 更为重要,这就是在本文中将要说到的 Event. 在 Ce ...

随机推荐

  1. SpringBoot 整合 JDBC 实例

    0.数据库表 CREATE DATABASE springboot; USE springboot; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_ ...

  2. Nvidia GPU热迁移-Singularity

    1 背景 在GPU虚拟化和池化的加持下,可以显著提高集群的GPU利用率,同时也可以较好地实现弹性伸缩.但有时会遇到需要GPU资源再分配的场景,此时亟需集群拥有GPU任务热迁移的能力.举个简单的例子,比 ...

  3. Proxy 与 Object.defineProperty 优劣对比?

    Proxy的优势如下 1.Proxy 可以直接监听对象而不是属性(Object.defineProperty一次只能监视一个属性,如果要监视一个对象,那么需要遍历这个对象),可以直接监听数组的变化(O ...

  4. 通过命令快速找到python的路径

    查询Python 首先我们需要知道Python安装路径,可以在命令行中逐行执行下面代码 python3 import sys sys.executable

  5. 【vue3-element-admin 】基于 Vue3 + Vite4 + TypeScript + Element-Plus 从0到1搭建后台管理系统(前后端开源@有来开源组织)

    vue3-element-admin 是基于 vue-element-admin 升级的 Vue3 + Element Plus 版本的后台管理前端解决方案,技术栈为 Vue3 + Vite4 + T ...

  6. AppScan-使用入门

    一.介绍 AppScan是IBM公司出的一款Web应用安全测试工具,采用黑盒测试的方式,可以扫描常见的web应用安全漏洞 工作原理 首先是根据起始页爬取站下所有可见的页面,同时测试常见的管理后台 获得 ...

  7. 小米商城主页展示HTML+CSS

    大佬们呀,花了好几天的时间总算是看着页面展示可以了,求赐教! 小米商城主页,对大佬来说肯定简单爆了,我抄写了好久呀,总是有一点点的小问题,还搞不明白 主要是一个静态的小米商城页面,HTML前端代码不复 ...

  8. 2023-03-07:x264的视频编码器,不用ffmpeg,用libx264.dll也行。请用go语言调用libx264.dll,将yuv文件编码成h264文件。

    2023-03-07:x264的视频编码器,不用ffmpeg,用libx264.dll也行.请用go语言调用libx264.dll,将yuv文件编码成h264文件. 答案2023-03-07: 使用 ...

  9. 2020-03-02:在无序数组中,如何求第K小的数?

    2020-03-02:在无序数组中,如何求第K小的数? 福哥答案2021-03-02: 1.堆排序.时间复杂度:O(N*lgK).有代码. 2.单边快排.时间复杂度:O(N).有代码. 3.bfprt ...

  10. gitlab-runner 中的 Docker-in-Docker

    笔者个人理解:gitlab-runner 安装后就是一个监听状态的 runner,而通过 gitlab-runner register 注册的"实例"其实只是预定义的配置节,当消息 ...