1.前言

本章介绍阻塞队列SynchronousQueue。之前介绍过LinkedTransferQueue,特点提供了让生产者知道消费者消费了其产出,没消费就等待的模式,本章介绍的这个类则必须是生产者生产后消费者消费了才会继续下去,反之亦然,消费者必须等待生产者产出。SynchronousQueue只有这一种模式,而LinkedTransferQueue是可选的,SynchronousQueue不存储元素,像接力棒一样,没有交接就一直等。所以其特定是没有容量,不能peek查看,如果没有消费者不能插入,不能遍历,该队列表现的就像一个空的集合。同样的,该队列不接受空元素。默认情况下,线程的等待唤醒是非公平的,可以设置成公平模式,保证线程是先入先出(先到先得)。通常两种模式的性能差不多,非公平模式可以维持更多的线程,公平模式则支持更高的吞吐量。

2.SynchronousQueue

2.1 实现原理

该类的实现是基于dual stack和dual queue算法,dual queue在LinkedTransferQueue中介绍过。queue和stack都包含数据节点和请求节点,其特点就是任何操作都能明确当前队列的所处模式(数据--没有被消费者消费或请求--没有生产者)。stack和queue都继承自抽象类Transferer,其定义了唯一方法transfer用来put或者take,在dual数据结构中,定义成一个方法原因在于put和take操作是对称的。

SynchronousQueue的实现与原算法有些不同的地方:1、原算法使用bit-marked指针,这里使用mode bits,导致了一系列的改动。2、SynchronousQueue会阻塞线程等待到装满。3、通过超时和中断,支持取消操作,包括清除所有取消节点/线程,避免垃圾存留或内存损耗。

阻塞操作大多通过LockSupport类的park或unpark方法,除非是在多核CPU上该节点看起来是下一个首个填满的结点,通过自旋一位。在非常忙碌的队列中,自旋可以显著提升吞吐量。cleaning操作在queue和stack中不同,queue中remove操作是O(1)时间,但是stack为O(n)时间。

2.2 数据结构

Transferer就是上面所说的抽象类,里面只有一个方法。后面也有Stack和Queue的实现。

NCPUS:当前主机CPU核数

maxTimedSpins:限时等待阻塞前自旋的次数,单核为0,多核32

maxUntimedSpins:不限时等待阻塞前自旋的次数,maxTimedSpins * 16

spinForTimeoutThreshold:纳秒数,这个为了更快的自旋而不是使用park时间。初略估计足够了,默认1000

transferer:具体使用的实现对象。

通过构造函数可以看出,公平模式使用的是queue,非公平模式使用的是stack,默认非公平。

2.3 基本操作

该类的基本操作都是基于transferer实现的,所以这里就不进行介绍。

存入取出的不同之处只在于第一参数是否是null,不为null就是存入,为null就是取出。所以该队列也不能存入null元素。其它的方法都是空。

2.4 TransferQueue

数据结构和之前所讲LinkedTransferQueue基本一致,方法也类似。主要看transfer(E,boolean,long)方法。基本的算法就是循环做两件事情:1、如果队列为空或者持有相同的模式的结点,尝试添加队列结点,等待fulfilled或cancelled,并返回匹配项。2、如果队列不为空,放入的和其模式相反,即可以匹配就通过CAS操作填充该节点的item字段并出队列,返回匹配项。

代码过长不给出,描述一下相关过长:

1、通过E来判断当前调用是一个什么模式的结点。

2、死循环处理:

1.头尾节点存在null,为初始化进行循环。

2.队列为空或模式一致:

判断t是否是当前的尾,不是意味丢失尾,重新循环

判断当前尾的下一个是否为null,不为null就是尾结点滞后了,重新设置尾结点,重新循环

不等待就返回null

创建该节点

设置尾结点的下一个节点失败,被抢先,重新循环

成功重置尾结点。

进行等待指定时间。

超时被取消,清除返回null。

丢失顺序,重置头

返回结果。

3.队列不为空且模式不一致:

头结点的下一个节点,如果为空或者头尾结点被改变了,读取不一致重新循环。

此刻没有乱序,取出节点的item,进行CAS操作判断是否被抢先了,被抢先了移除该节点,继续循环尝试。

成功了移除该节点,解除waiter的等待。

2.5 TransferStack

stack的结点数据结构,和queue的有些不同,就是多了一个match结点。node有四个方法:1、CAS设置next结点。2、CAS设置match结点,返回匹配结构。3、取消当前结点。4、返回当前结点是否取消。

transfer方法的逻辑和queue的类似,stack的transfer循环需要做三件事情:1、如果栈为空或者模式相同,生成结点入栈等待匹配,返回结果或空如果超时。2、如果栈不为空且模式不同,匹配等待的结点,两个都出栈,返回匹配值。由于其他线程可能执行第3点,匹配或者断开连接可能不是必须的。3、如果栈顶元素匹配成功,帮助其出栈匹配,然后继续循环。

整个流程就是上面3点,其他的照着看代码应该较为简单,和queue的思路差不多。由于第三点需要帮助其他线程出栈,这个过程可能被其它后到线程抢先,所以是非公平的。

3.使用例子

@Test

public void testSynchronous() {

SynchronousQueue queue = new SynchronousQueue<>();

System.out.println(queue.offer(1));// 立即返回,必须要有消费者

System.out.println(queue.poll());// 立即返回,必须要有生产者

long start = System.currentTimeMillis();

new Thread(new Runnable() {

@Override

public void run() {

try {

System.out.println(Thread.currentThread().getName()+"-" +

queue.take()+",耗时:"+(System.currentTimeMillis()-start)); // 没有生产者一直阻塞

Thread.sleep(2000);

System.out.println(Thread.currentThread().getName()+"-" + queue.take());

Thread.sleep(1500);

System.out.println(Thread.currentThread().getName()+"-" + queue.poll(1, TimeUnit.SECONDS));

} catch (InterruptedException e1) {

e1.printStackTrace();

}

}

},"consumer").start();

new Thread(new Runnable() {

@Override

public void run() {

try {

Thread.sleep(1000);

System.out.println(Thread.currentThread().getName()+"-等待2被消耗:"+queue.offer(2));

System.out.println(Thread.currentThread().getName()+"-等待3被消耗:");

long one = System.currentTimeMillis();

queue.put(3);

System.out.println("3被消耗,耗时:" + (System.currentTimeMillis() - one));

System.out.println(Thread.currentThread().getName()+"-等待4被消耗:" +

queue.offer(4, 1, TimeUnit.SECONDS));

} catch (InterruptedException e) {

e.printStackTrace();

}

}

},"prodcuer").start();

try {

System.in.read();

} catch (IOException e) {

e.printStackTrace();

}

}

java 超时集合_Java之集合(二十三)SynchronousQueue相关推荐

  1. java 有序容器_Java 容器集合框架概览

    Java Collections Framework 集合的概念 集合collection,有时叫做容器container,把多个元素组成一个单元. 早期的Java (pre-1.2) 中包含了Vec ...

  2. java中的集合_Java中集合中的基本概念

    集合:保存多个其他对象的对象,不能保存简单类型. Collection框架的结构如下: Collection是最基本的集合接口,一个Collection代表一组object,即Collection的元 ...

  3. java 自定义运算符_Java中集合的自定义运算符

    java 自定义运算符 总览 操作员重载有多种语言可用. Java对String类型的+运算符的支持对运算符的重载非常有限. 我们可以利用其他语言支持运算符的不同方式,但是我们可以在Java中实现一个 ...

  4. java线性表与集合_Java之集合

    为什么我们要使用集合?之前存储大量数据使用数组,数组不能拓展容量.数组在操作元素时需要大量的移动元素,操作元素的逻辑需要开发者写代码实现.实际开发过过程中,需要自动拓容的容器. Collection集 ...

  5. java创建集合_java创建集合的常用格式

    创建集合的常用格式: 导包:import java.util.ArrayList; 创建对象:与其他普通的引用数据类型创建方式完全相同,但是要指定容器中存储的数据类型: ArrayList 变量名 = ...

  6. java基础习题集_java基础集合经典训练题

    第一题:要求产生10个随机的字符串,每一个字符串互相不重复,每一个字符串中组成的字符(a-zA-Z0-9)也不相同,每个字符串长度为10; 分析:*1.看到这个题目,或许你脑海中会想到很多方法,比如判 ...

  7. java arraylist范围_Java常见集合之ArrayList深入分析

    1 /* 2 继承自AbstractList,实现了List.RandomAccess.Cloneable.Serializable接口3 1)RandomAccess接口:用来快速随机存取,在实现了 ...

  8. java 最接近_Java在集合中查找最接近(或相等)的值

    我有一个类: public class Observation { private String time; private double x; private double y; //Constru ...

  9. java 判断 子集_java – 获取集合子集的策略

    我有一个场景,我的应用程序可以访问有限时间窗口的会话,在此期间它必须从数据库中获取数据到内存中,然后只使用内存中的数据来处理请求. 数据模型是一个简单的一对多关联,例如: 现在假设汽车和卡车计数数据存 ...

最新文章

  1. Python 之 Numpy (五)合并
  2. Windows 下面的 redis GUI操作工具
  3. 26、HTML 区块
  4. 翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器
  5. 【项目管理】人力资源管理
  6. csdn开发者报告中学习到的新知识
  7. 关于VS环境下制作和使用静态库和动态库
  8. 51nod---无法表示的数
  9. MPLS ×××实验之OSPF sham-link
  10. mysql的告警日志_MySQL Aborted connection告警日志的分析
  11. java加载配置文件
  12. 对于scanf,strcpy等函数报4996错误的粗暴而简单解决办法
  13. 笨办法学 Python · 续 练习 49:`sed`
  14. 解决Windows 2003终端服务许可证过期的办法
  15. 可视化类激活的热力图
  16. YUV 格式与 RGB 格式的相互转换公式总结(C++版)
  17. lion.ec开源框架简介(原创)
  18. C++ 中map容器
  19. ffmpeg代码实现自定义decoder
  20. [2018.10.11 T3] 欠钱

热门文章

  1. 男人“杀”死女人的30句话
  2. 程序员每天少吃 能活120岁
  3. 程序员的乐趣从哪来?编程能给我带来乐趣吗?
  4. html 从左往右消失,从左到右语言写成从右到左html
  5. bean json转kotlin_Android--------kotlin插件神器Json直接生成javaBean
  6. C\C++不经意间留下的知识空白------const使用
  7. 送书《R语言数据分析和可视化》 | 这个为生信学习和生信作图打造的开源R教程真香!!!...
  8. 将自己名字PS到他人论文上,并推文说在Nature上发了新文章,如此操作你见过吗?...
  9. 基因组中的趣事(一):这个基因编码98种转录本
  10. 诺奖奖金为何119年还没发完?