《java.util.concurrent 包源码阅读》18 Exchanger
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的更多相关文章
- 《java.util.concurrent 包源码阅读》 结束语
<java.util.concurrent 包源码阅读>系列文章已经全部写完了.开始的几篇文章是根据自己的读书笔记整理出来的(当时只阅读了部分的源代码),后面的大部分都是一边读源代码,一边 ...
- 《java.util.concurrent 包源码阅读》13 线程池系列之ThreadPoolExecutor 第三部分
这一部分来说说线程池如何进行状态控制,即线程池的开启和关闭. 先来说说线程池的开启,这部分来看ThreadPoolExecutor构造方法: public ThreadPoolExecutor(int ...
- 《java.util.concurrent 包源码阅读》02 关于java.util.concurrent.atomic包
Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的A ...
- 《java.util.concurrent 包源码阅读》04 ConcurrentMap
Java集合框架中的Map类型的数据结构是非线程安全,在多线程环境中使用时需要手动进行线程同步.因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:Concu ...
- 《java.util.concurrent 包源码阅读》17 信号量 Semaphore
学过操作系统的朋友都知道信号量,在java.util.concurrent包中也有一个关于信号量的实现:Semaphore. 从代码实现的角度来说,信号量与锁很类似,可以看成是一个有限的共享锁,即只能 ...
- 《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue
对于BlockingQueue的具体实现,主要关注的有两点:线程安全的实现和阻塞操作的实现.所以分析ArrayBlockingQueue也是基于这两点. 对于线程安全来说,所有的添加元素的方法和拿走元 ...
- 《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇
concurrent包中Executor接口的主要类的关系图如下: Executor接口非常单一,就是执行一个Runnable的命令. public interface Executor { void ...
- 《java.util.concurrent 包源码阅读》05 BlockingQueue
想必大家都很熟悉生产者-消费者队列,生产者负责添加元素到队列,如果队列已满则会进入阻塞状态直到有消费者拿走元素.相反,消费者负责从队列中拿走元素,如果队列为空则会进入阻塞状态直到有生产者添加元素到队列 ...
- 《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService
AbstractExecutorService对ExecutorService的执行任务类型的方法提供了一个默认实现.这些方法包括submit,invokeAny和InvokeAll. 注意的是来自E ...
随机推荐
- 前端笔记----jquery入门知识点总结
一.jquery的加载方法 $(document).ready(function(){js代码}); $(function(){js代码});(一般使用这个); 注意点1:使用jquery必须先导入函 ...
- java基础--static关键字的使用
一.static关键字的作用 使类成员完全独立于该类的任何对象.通常情况下,类成员必须通过它的类的对象访问,但是被static修饰的成员,能够被自己访问,而不必引用特定的实例. 一旦一个成员被声明为s ...
- win10 uwp 九幽图床
本文主要是图片加水印自动上传,代码已经上传github. 图片加水印,我的方法,简单,一个好的方法是毒逆天大神的图片加水印方法或http://daily.zhihu.com/story/8812028 ...
- 关于限制DHCP服务器广播的另类方法
0. 引言 如图,家里有两台计算机,分别处于不同房间.其中一台PC1能够与光猫直连:另外一台PC2较远,考虑到光猫有WLAN功能,便使用了一个路由器无线桥接到光猫,然后有线连接到PC2(均与光猫处于同 ...
- CSS样式之表格,表单
布局样式 .container:固定宽度并具响应式 .container-fluid自由宽度(100%宽度) 标题样式 <h1>到<h6> 样式已经写好了,可以直接用,兼容性也 ...
- OMP算法代码学习
正交匹配追踪(OMP)算法的MATLAB函数代码并给出单次测试例程代码 测量数M与重构成功概率关系曲线绘制例程代码 信号稀疏度K与重构成功概率关系曲线绘制例程代码 参考来源:http://blog ...
- django(注册→登录→主页)增强版
首先准备一张空白的数据表: urls展示: views主要的几个函数以及数据库链接代码:↓ 后端编写结束↑ ↓前端 前端界面:↓ 幸好成功了,接下来看看数据库有没有插入数据.... 这么 ...
- MongoDB索引限制
1. 额外开销: 每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作.所以,如果你很少对集合进行读取操作,建议不使用索引. 2. 内存使用: 由于索引是存储在内存(RAM)中 ...
- MongoDB原子操作
MongoDB原子操作常用命令: 1. $set: 用来指定一个键并更新键值,若键不存在则创建并赋值. { $set : { field : value } } 2. $unset: 用来删除一个键. ...
- 分布式学习(一)——基于ZooKeeper的队列爬虫
zookeeper 一直琢磨着分布式的东西怎么搞,公司也没有相关的项目能够参与,所以还是回归自己的专长来吧--基于ZooKeeper的分布式队列爬虫,由于没什么人能够一起沟通分布式的相关知识,下面的小 ...