Exchanger可以看做双向数据传输的SynchronousQueue,即没有生产者和消费者之分,任意两个线程都可以交换数据。

在JDK5中Exchanger被设计成一个容量为1的容器,存放一个等待线程,直到有另外线程到来就会发生数据交换,然后清空容器,等到下一个到来的线程。

从JDK6开始,Exchanger用了类似ConcurrentMap的分段思想,提供了多个slot,增加了并发执行时的吞吐量。

Exchanger不存在公平不公平的模式,因为没有排队的情况发生,只要有两个线程就可以发生数据交换。

直接看核心方法:

    private Object doExchange(Object item, boolean timed, long nanos) {
Node me = new Node(item);
// index是线程ID的hash值映射到0到max之间的一个值
// 一般情况下max为0,这样线程交换数据只会使用第一个slot,
// 即index是0,而max不为0情况请看下面的循环
int index = hashIndex(); // CAS操作失败的次数
int fails = 0; for (;;) {
// 当前slot中存储的对象,也就是Node
Object y;
Slot slot = arena[index];
// 延迟加载,即只有当slot为null时才创建一个slot
// 延迟加载后重新循环一次
if (slot == null)
createSlot(index);
// slot中有数据,也就意味着有线程在等待交换数据
// 这时可以尝试用CAS重置slot(把slot存储的对象设为null)
// 用slot中存储的对象和当前线程进行数据交换
// 如果交换成功就通知原先等待的线程
else if ((y = slot.get()) != null &&
slot.compareAndSet(y, null)) {
Node you = (Node)y;
if (you.compareAndSet(null, item)) {
LockSupport.unpark(you.waiter);
return you.item;
}
// 如果slot存储的对象已经被重置为null,但是数据交换失败了
// 这时就意味着这个等待的线程的交换请求被取消了
// 在分析wait类型的方法代码时会看到如何处理这种情况
}
// 如果slot中没有存储对象,那么首先尝试把当前线程存储到slot中
// 如果存储失败了,就重新循环
else if (y == null &&
slot.compareAndSet(null, me)) {
// index为0意味着仅仅有当前线程在等待交换数据,因此直接等待即可
if (index == 0)
return timed ?
awaitNanos(me, slot, nanos) :
await(me, slot);
// 所谓的spin wait:就是固定次数循环,每次计数减一
// 对于单核系统来说,spin wait是不做的,因为单核
// 做wait时需要占用CPU,其他线程是无法使用CPU,因此这样
// 的等待毫无意义。而多核系统中spin值为2000,也就是会做
// 2000次循环。
// 如果循环完成后依然没有得到交换的数据,那么会返回一个
// CANCEL对象表示请求依旧被取消,并且把Node从slot中清除
Object v = spinWait(me, slot);
if (v != CANCEL)
return v;
// 如果取消了,就新建一个Node取消原先取消的Node用于下次循环
me = new Node(item);
int m = max.get();
// index除2,缩小slot的范围
// 同时如果m过大,减小m
if (m > (index >>>= 1))
max.compareAndSet(m, m - 1);
}
// 允许CAS失败两次,因为两个else if中都有CAS,因此这里
// 允许两个else if的CAS操作都失败过
else if (++fails > 1) {
int m = max.get();
// 失败超过3次,增大m,并且从m处重新索引
if (fails > 3 && m < FULL && max.compareAndSet(m, m + 1))
index = m + 1;
// 当index小于0,回到m,重新循环
else if (--index < 0)
index = m;
}
}
}

这篇文章关于索引index这块弄得不是很清楚,后续会继续研究,及时更新。

《java.util.concurrent 包源码阅读》18 Exchanger的更多相关文章

  1. 《java.util.concurrent 包源码阅读》 结束语

    <java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...

  2. 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分

    这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...

  3. 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包

    Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...

  4. 《java.util.concurrent 包源码阅读》04 ConcurrentMap

    Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...

  5. 《java.util.concurrent 包源码阅读》17 信号量 Semaphore

    学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...

  6. 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue

    对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...

  7. 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇

    concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...

  8. 《java.util.concurrent 包源码阅读》05 BlockingQueue

    想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...

  9. 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService

    AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...

随机推荐

  1. PHP 支付

    蚂蚁金服开放平台 2.下载PHP的SDK&demo 3.申请应用 OR 使用沙箱环境 4.生成应用私钥&应用公钥 5.配置config.php 蚂蚁金服开放平台",对,没错, ...

  2. 浅谈python 复制(深拷贝,浅拷贝)

    博客参考:点击这里 python中对象的复制以及浅拷贝,深拷贝是存在差异的,这儿我们主要以可变变量来演示,不可变变量则不存在赋值/拷贝上的问题(下文会有解释),具体差异如下文所示 1.赋值: a=[1 ...

  3. Django项目搭建和配置总结

    安装和创建虚拟环境 参考:linux系统下Python虚拟环境的安装和使用 安装Django包 先进入虚拟环境,在联网下执行: pip install django==1.8.7 1.8.7表示dja ...

  4. APP在模拟器崩溃,提示__gcd_queue_item_enqueue_hook_block_invoke

    libBacktraceRecording.dylib`__gcd_queue_item_enqueue_hook_block_invoke: libBacktraceRecording.dylib` ...

  5. Spark 作业调度相关术语

    作业(Job):RDD 中由行动操作所生成的一个或多个调度阶段 调度阶段(Stage):每个作业会因为 RDD 间的依赖关系拆分成多组任务集合,称为调度阶段,也叫做任务集(TaskSet).高度阶段的 ...

  6. VPS搭建离线下载服务器——后网盘时代

    动机 由于学习的需要,在国外某服务器厂商购买了vps服务(至于是哪个厂商就不说啦).但是呢,就算用作梯子,一个月1T的流量总是用不完.最经觉得自己营养充足,想找点电影看看. 无奈现在百度网盘的速度真的 ...

  7. 电脑键盘上的F键有什么用 电脑F键功能讲解

    接触电脑这么多年了,F1到F12这几个键你真的会用吗?电脑键盘上的F键有什么用?你了解过吗?这里带来电脑F键功能讲解,一起来看看. F1:帮助 在程序里或者资源管理器界面,按F1会弹出帮助按钮. F2 ...

  8. spark streaming 实例

    spark-streaming读hdfs,统计文件中单词数量,并写入mysql package com.yeliang; import java.sql.Connection; import java ...

  9. Python爬虫入门:爬虫基础了解

    有粉丝私信我想让我出更基础一些的,我就把之前平台的copy下来了,可以粗略看一下,之后都会慢慢出. 1.什么是爬虫 爬虫,即网络爬虫,大家可以理解为在网络上爬行的一直蜘蛛,互联网就比作一张大网,而爬虫 ...

  10. nova创建虚拟机源码分析系列之七 传入参数转换成内部id

    上一篇博文将nova创建虚机的流程推进到了/compute/api.py中的create()函数,接下来就继续分析. 在分析之前简单介绍nova组件源码的架构.以conductor组件为例: 每个组件 ...