1. 前言

Java 中总的算起来有 8 种阻塞队列。

我们分析了:

ArrayBlockingQueue 数组队列,我们在 使用 ReentrantLock 和 Condition 实现一个阻塞队列 看过了 JDK 写的一个例子,就是该类的基本原理和实现。楼主不准备分析了。

LinkedBlockingDeque 是一个双向链表的队列。常用于 “工作窃取算法”,有机会再分析。

DelayQueue 是一个支持延时获取元素的无界阻塞队列。内部用 PriorityQueue 实现。有机会再分析。

PriorityBlockingQueue 是一个支持优先级的无界阻塞队列,和 DelayWorkQueue 类似。有机会再分析。

今天要分析的是剩下的一个比较有意思的队列:LinkedTransferQueue

为什么说有意思呢?他可以算是 LinkedBolckingQueueSynchronousQueue 和合体。

我们知道 SynchronousQueue 内部无法存储元素,当要添加元素的时候,需要阻塞,不够完美,LinkedBolckingQueue 则内部使用了大量的锁,性能不高。

两两结合,岂不完美?性能又高,又不阻塞。

我们一起来看看。

2. LinkedTransferQueue 介绍

该类实现了一个 TransferQueue。该接口定义了几个方法:

public interface TransferQueue<E> extends BlockingQueue<E> {
// 如果可能,立即将元素转移给等待的消费者。
// 更确切地说,如果存在消费者已经等待接收它(在 take 或 timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则返回 false。
boolean tryTransfer(E e); // 将元素转移给消费者,如果需要的话等待。
// 更准确地说,如果存在一个消费者已经等待接收它(在 take 或timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则等待直到元素由消费者接收。
void transfer(E e) throws InterruptedException; // 上面方法的基础上设置超时时间
boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException; // 如果至少有一位消费者在等待,则返回 true
boolean hasWaitingConsumer(); // 返回等待消费者人数的估计值
int getWaitingConsumerCount();
}

相比较普通的阻塞队列,增加了这么几个方法。

3. 关键源码分析

阻塞队列不外乎put ,take,offer ,poll等方法,再加上TransferQueue的 几个 tryTransfer 方法。我们看看这几个方法的实现。

put 方法:

public void put(E e) {
xfer(e, true, ASYNC, 0);
}

take 方法:

public E take() throws InterruptedException {
E e = xfer(null, false, SYNC, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}

offer 方法:

public boolean offer(E e) {
xfer(e, true, ASYNC, 0);
return true;
}

poll 方法:

public E poll() {
return xfer(null, false, NOW, 0);
}

tryTransfer 方法:

public boolean tryTransfer(E e) {
return xfer(e, true, NOW, 0) == null;
}

transfer 方法:

public void transfer(E e) throws InterruptedException {
if (xfer(e, true, SYNC, 0) != null) {
Thread.interrupted(); // failure possible only due to interrupt
throw new InterruptedException();
}
}

可怕,所有方法都指向了 xfer 方法,只不过传入的不同的参数。

第一个参数,如果是 put 类型,就是实际的值,反之就是 null。

第二个参数,是否包含数据,put 类型就是 true,take 就是 false。

第三个参数,执行类型,有立即返回的 NOW,有异步的 ASYNC,有阻塞的 SYNC, 有带超时的 TIMED

第四个参数,只有在 TIMED 类型才有作用。

So,这个类的关键方法就是 xfer 方法了。

4. xfer 方法分析

源码加注释:

private E xfer(E e, boolean haveData, int how, long nanos) {
if (haveData && (e == null))
throw new NullPointerException();
Node s = null; // the node to append, if needed retry:
for (;;) { // restart on append race
// 从 head 开始
for (Node h = head, p = h; p != null;) { // find & match first node
// head 的类型。
boolean isData = p.isData;
// head 的数据
Object item = p.item;
// item != null 有 2 种情况,一是 put 操作, 二是 take 的 itme 被修改了(匹配成功)
// (itme != null) == isData 要么表示 p 是一个 put 操作, 要么表示 p 是一个还没匹配成功的 take 操作
if (item != p && (item != null) == isData) {
// 如果当前操作和 head 操作相同,就没有匹配上,结束循环,进入下面的 if 块。
if (isData == haveData) // can't match
break;
// 如果操作不同,匹配成功, 尝试替换 item 成功,
if (p.casItem(item, e)) { // match
// 更新 head
for (Node q = p; q != h;) {
Node n = q.next; // update by 2 unless singleton
if (head == h && casHead(h, n == null ? q : n)) {
h.forgetNext();
break;
} // advance and retry
if ((h = head) == null ||
(q = h.next) == null || !q.isMatched())
break; // unless slack < 2
}
// 唤醒原 head 线程.
LockSupport.unpark(p.waiter);
return LinkedTransferQueue.<E>cast(item);
}
}
// 找下一个
Node n = p.next;
p = (p != n) ? n : (h = head); // Use head if p offlist
}
// 如果这个操作不是立刻就返回的类型
if (how != NOW) { // No matches available
// 且是第一次进入这里
if (s == null)
// 创建一个 node
s = new Node(e, haveData);
// 尝试将 node 追加对队列尾部,并返回他的上一个节点。
Node pred = tryAppend(s, haveData);
// 如果返回的是 null, 表示不能追加到 tail 节点,因为 tail 节点的模式和当前模式相反.
if (pred == null)
// 重来
continue retry; // lost race vs opposite mode
// 如果不是异步操作(即立刻返回结果)
if (how != ASYNC)
// 阻塞等待匹配值
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
return e; // not waiting
}
}

代码有点长,其实逻辑很简单。

逻辑如下:

找到 head 节点,如果 head 节点是匹配的操作,就直接赋值,如果不是,添加到队列中。

注意:队列中永远只有一种类型的操作,要么是 put 类型, 要么是 take 类型.

整个过程如下图:

相比较 SynchronousQueue 多了一个可以存储的队列,相比较 LinkedBlockingQueue 多了直接传递元素,少了用锁来同步。

性能更高,用处更大。

5. 总结

LinkedTransferQueue SynchronousQueueLinkedBlockingQueue 的合体,性能比 LinkedBlockingQueue 更高(没有锁操作),比 SynchronousQueue 能存储更多的元素。

put 时,如果有等待的线程,就直接将元素 “交给” 等待者, 否则直接进入队列。

put transfer 方法的区别是,put 是立即返回的, transfer 是阻塞等待消费者拿到数据才返回。transfer 方法和 SynchronousQueue 的 put 方法类似。

并发编程—— LinkedTransferQueue的更多相关文章

  1. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

  2. Java并发编程-总纲

    Java 原生支持并发,基本的底层同步包括:synchronized,用来标示一个方法(普通,静态)或者一个块需要同步执行(某一时刻,只允许一个线程在执行代码块).volatile,用来标识一个变量是 ...

  3. Java并发编程75道面试题及答案

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  4. [Java并发编程(五)] Java volatile 的实现原理

    [Java并发编程(五)] Java volatile 的实现原理 简介 在多线程并发编程中 synchronized 和 volatile 都扮演着重要的角色,volatile 是轻量级的 sync ...

  5. Java并发编程75个问答

    1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon( ...

  6. Java并发编程73道面试题及答案

    原文出处:https://blog.csdn.net/qq_34039315/article/details/7854931 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线 ...

  7. Java并发编程73道面试题及答案 —— 面试稳了

    今天主要整理一下 Java 并发编程在面试中的常见问题,希望对需要的读者有用. 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程(Daemon)和用户线程(User). 任 ...

  8. Java并发编程--BlockingQueue

    概述 BlockingQueue支持两个附加操作的Queue:1)当Queue为空时,获取元素线程被阻塞直到Queue变为非空:2)当Queue满时,添加元素线程被阻塞直到Queue不满.Blocki ...

  9. Java并发编程指南

    多线程是实现并发机制的一种有效手段.在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable/Callable 接口. java.util.concurre ...

随机推荐

  1. EBS-如何查看非自己提交的请求的结果

    http://www.cnblogs.com/quanweiru/p/4692071.html 如何查看非自己提交的请求的结果定位要找的请求SQL举例:SELECT req.request_id,   ...

  2. Android-Java-单例模式优化&多线程并发

    上一篇博客,Android-Java单例模式,介绍了在真实开发过程中,普遍使用的几种单例模式,而今天这篇博客,将要对单利模式进行优化 并且 通过多线程并发来分析 单利模式的优化: 对于为什么会出现安全 ...

  3. C#后台代码获取程序集资源文件

    资源会被打包在程序集内部. 选择这种生成方式后,该资源文件会被嵌入到该应用的程序集中,就是说打开生成的应用程序目录是看不到这个文件的. 可以用相对于当前的XAML文件的相对Uri访问,<Imag ...

  4. Color the ball(HDU1556)树状数组

    每次对区间内气球进行一次染色,求n次操作后后所有气球染色次数. 树状数组,上下区间更新都可以,差别不大. 1.对于[x,y]区间,对第x-1位减1,第y位加1,之后向上统计 #include<b ...

  5. css居中小结

    从css入门就开始接触,无所不在的,一直备受争议的居中问题. css居中分为水平居中和垂直居中,水平居中方式也较为常见和统一,垂直居中的方法就千奇百怪了. 博客原文地址:Claiyre的个人博客 ht ...

  6. [Visual Studio] 记一次排错:打不开 Nuget 包管理器里的安装package页面,无法 安装 / 恢复 包

    出差一阵子PC很久没用了, 回来在用Visual Studio (2017) 的时候, 工程里无法安装Package了  >_<    --- 也不知道电脑做了什么?--- Nuget包管 ...

  7. joi库 学习笔记

    零.背景 node.js 应用中,req.query / req.body 传来的参数需要做 valication( 合法性验证 ) 一.安装 https://github.com/hapijs/jo ...

  8. oracle安装完成之后,执行sqlplus命令,提示:bash: sqlplus: command not found...

    1.安装完oracle后,用终端登录,因为不是直接用oracle用户登录的,所以我就执行$ su oracle切换成oracle用户之后,执行"echo $ORACLE_HOME" ...

  9. oracle undo redo 解析

    Undo是干嘛用的?          简单理解,就相当于Windows下的回收站.        你对数据执行修改时,数据库会生成undo信息,这样万一你执行的事务或语句由于某种原因失败了,或者如果 ...

  10. Xamarin 绑定安卓第三方库恢复原始参数问题

    大家都知道在绑定xamarin android 第三方库的时候 参数名是乱码的 变成了p1  p2  p3 之类的 这样在实际使用的时候非常不方便. 其实xamarin是提供了三种方式帮助大家恢复ja ...