广州住房公积金管理中心,参考消息-ope_ope体育app下载_ope体育客户端官方下载

问题

(1)丝袜视频LinkedTransferQueue是什么东东?

(2)LinkedTransferQueue是怎样完成堵塞行列的?

(3)LinkedTransferQueue是怎样操控并发安全的?

(4)LinkedTransferQueue与SynchronousQueue有什么异同?

简介

LinkedTransferQueue是LinkedBlockingQueue、SynchronousQueue(公正形式)、ConcurrentLinkedQueue三者的集合体,它归纳了这三者的办法,而且供给了愈加高效的完成办法。

承继系统

LinkedTransferQueue完成了TransferQueue接口,而TransferQueue接口是承继自BlockingQueue的,所以Lin千年血玉kedTransferQueue也是一个堵塞行列。

TransferQueue接口中界说了以下几个办法:

// 测验移送元素
boolean tryTransfer(E e);
// 移送元素
void transfer(E e) throws InterruptedException;
// 测验移送元素(有超时时刻)
boolean tryTransfer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
// 判别是否有消玥玥児费者
boolean hasWaitingConsumer();
// 检查顾客的数量
int getWaitingConsumerCount();

首要是界说了三个移送元素的办法,有堵塞的,有不堵塞的,有超时的。

存储结构

LinkedTransferQueue运用了一个叫做dual data structure的数据结构,或许叫做dual queue,译为两层数据结构或许两层行列。

两层行列是什么意思呢?

放取元素运用同一个行列,行列中的节点具有两种形式,一种是数据节点,一种对错数据节点。

放元素时先跟行列头节点比照,假如头节点对错数据节点,就让他们匹配,假如头节点是数据节点,就生成一个数据节点放在行列尾端(入队)。

取元素时也是先跟行列头节点比照,假如头节点是数据节点,就让他们匹配,假如头节点对错数据节点,就生成一个非数据节点放在行列尾端(入队)。

用图形来表明便是下面这样:

不论是放元素仍是取元素,都先跟头节点比照,假如二者形式不相同就匹配它们,假如二者形式相同,就入队。

源码剖析

首要特点

// 头节点
transient volatile Node head;
// 尾节点
private transient volatile Node tail;
// 放取元素的几种办法:
// 当即回来,用于非超时的poll()和tryTransfer()办法中
private static final int NOW = 0; // for untimed poll, tryTransfer
// 异步,不会堵塞,用于放元素时,由于内部运用无界单链表存储元素,不会堵塞放元素的进程
private static final int AS广州住宅公积金管理中心,参考消息-ope_ope体育app下载_ope体育客户端官方下载YNC = 1; // for offer, put, add
// 同步,调用的时分假如没有匹配到会堵塞直到匹配到停止
private static final int SYNC = 2; // for transfer, take
// 超时,用于有超时的poll()和tryTransfer()办法中
private static final int TIMED = 3; // for timed poll, tryTransfer

首要内部类

static final class Node {
// 是否是数据节点(也就标识了是生产者仍是顾客)
final boolean isData; // false if this is a request node
// 元素的值
volatile Object item; // initially non-null if isData; CASed to match
// 下一个节点
volatile Node next;
// 持有元素的线程
volatile Thread waiter; // null until waiting
}

典型的单链表结构,内部除了存储元素的值和下一个节点的指针外,还包含了是否为数据节点和持有元素的线程。

内部经过isData差异是生产者仍是顾客。

首要结构办法

public LinkedTransferQueue() {
}
public LinkedTransferQueue(Collection
this();
addAll(c);
}

只要这两个结构办法,且没有初始容量,所以是无界的一个堵塞行列。

入队

四个办法都是相同的,运用异步的办法调用xfer()办法,传入的参数都一模相同。

public void put(E e) {
// 异步形式,不会堵塞,不会超时
// 由于是放元素,单链表存储,会一向往后加
xfer(e, true, ASYNC, 0);
}
public boolean offer(E e, long timeout, TimeUnit unit) {
xfer(e, true, ASYNC香椿, 0);
return true;
}
public boolean offer(E e) {
xfer(e, true, ASYNC, 0);
return true;
}
public boolean add(E e) {
xfer(e, true, ASYNC, 0);
return true;
}

xfer(E e, boolean haveData, int how, long nanos)的参数分别是:

(1)e表明元素;

(2)hav肺栓塞eData表明是否是数据节点,

(3)how表明放取元素的办法,上面说到的四种,NOW、ASYNC、SYNC、TIMED;

(4)nanos表明超时时刻;

出队

出队的四个办法也是直接或直接的调用xfer()办法,放取元素的办法和超时规矩稍微不同,实质没有大的差异。

public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E take() throws InterruptedException {
// 同步形式,会堵塞直到取到元素
E e = xfer(null, false, SYNC, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
// 有超时时刻
E e = xfer(null, false, TIMED, unit.toNanos(timeout));
if (e != null || !Thread.interrupted())
return e;
throw new InterruptedException();
}
public E poll() {
// 当即回来,没取到元素回来null
return xfer(null, false, NOW, 0);
}

取元素就各有各的玩法了,有同步的,有超时的,有当即回来的。

移送元素的办法

public boolean tryTransfer(E e) {
// 当即回来
return xfer(e, true, NOW, 0) == null;
}
public void tran鲅鱼圈sfer(E e) throws InterruptedEx湖南卫视小年夜春晚ception {
// 同步形式
if (xfer(e, true, SYNC, 0) != null) {
Thread.interrupted(); // failure possible only due to interrupt
throw new InterruptedException();
}
}
public boolean tryTransfer(E e, long timeout, TimeUnit unit)
thro笠哀ws InterruptedException {
// 有超时时刻
if (xfer(e, true, TIMED, unit.toNanos(timeout)) == null)
return true;
if (!Thread.interrupted())
return false;
throw new InterruptedException();
}

请留意第二个参数,都是true,也便是这三个办法其实也是放元素的办法。

这儿xfer()办法的几种形式到底有什么差异呢?请看下面的剖析。

奇特的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
// 下面这个for循环用于操控匹配的进程
// 同一时刻行列中只会存储一种类型的节点
// 从头节点开端测验匹配,假如头节点被其它线程先一步匹配了
// 就再测验其下一个,直到匹配到停止,或许到行列中没有元素停止

for (Node h = head, p = h; p != null;) { // find & match first node
// p节点的形式
boolean isData = p.isData;
// p节点的值
O5200bject item = p.item;
// p没有被匹配到
if (item != p && (item != null) == isData) { // unmatched
// 假如两者形式相同,则不能匹配,跳出循环后测验入队
if (isData == haveData) // can't match
break;
// 假如两者形式不相同,则测验匹配
// 把p的值设置为e(假如是取元素则e是null,假如是放元素则e是元素值)
if (p.casItem(item, e)) { // match
// 匹配成功
// for里边的逻辑比较杂乱,用于操控多线程一起放取元素时呈现竞赛的状况的
// 看不懂能够直接越过
for (Node q = p; q != h;) {
// 进入到这儿或许是头节点现已被匹配,然后p会变成h的下一个节点
Node n = q.next; // update by 2 unless singleton
// 假如head还没变,就把它更新成新的节点
// 并把它删去(forgetNext()会把它的next设为自己,也便是从单链表中删去了)
// 这时为什么要把head设为n呢?由于到这儿了,必定head自身现已被匹配掉了
// 而上面的p.casItem()又成功了,阐明p也被当时这个元素给匹配掉了
// 所以需求把它们俩都出行列,让其它线程能够从真实的头开端,不必重复检查了
if (head == h && casHead(h, n == null ? q : n)) {
h.forgetNext();
break;
} // advance and retry
// 假如新的头节点为空,或许其next为空,或许其next未匹配,就重试
if ((h = head) == null ||
(q = h.next) == null || !q.isMatched())
break; // unless slack < 2
}
// 唤醒p中等候的线程
LockSupport.unpark(p.waiter);
// 并回来匹配到的元素
return LinkedTransferQueue.cast(item);
}
}
// p现已被匹配了或许测验匹配的时分失利了
// 也便是其它线程先一步匹配了p
// 这时分又分两种状况,p的next还没来得及修正,p的next指向了自己
// 假如p的next现已指向了自己,就从头取head重试,不然就取其next重试
Node n = p.next;
p = (p != n) ? n : (h = head); // Use head if p offlist
}
// 到这儿必定是行列中存储的节点类型和自己相同
// 或许行列中没有元素了
// 就入队(不论放元素仍是取元素都得入队)
// 入队又分红四种状况:
// NOW,当即回来,没有匹配到当即回来,不做入队操作
// ASYNC,异步,元素入队但当时线程不会堵塞(相当于无界LinkedBlockingQueue的元素入队)
// SYNC,同步,元素入队后当时线程堵塞,等候被匹配到
// TIMED,有超时,元素入队后等候一段时刻被匹配,时刻到了还没匹配到就回来元素自身
// 假如不是当即回来
if (how != NOW) { // No matches available
// 新建s节点
if (s == null)
s = new Node(e, haveData);
// 测验入队
Node pred = tryAppend(s, haveData);
// 入队失利,重试
if (pred == null)
continue retry; // lost race vs鸟巢 opposite mode
// 假如不是异步(同步或许有超时)
// 就等候被匹配
if (h北三县ow != ASYNC)
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
return e; // not waiting
}
}
private Node tryAppend(Node s, boolean haveData) {
// 从tail开端遍历,把s放到链表尾端
for (Node t = tail, p = t;;) { // move p to last node and append
Node n, u; //广州住宅公积金管理中心,参考消息-ope_ope体育app下载_ope体育客户端官方下载 temps for reads of next & tail
// 假如首尾都是null,阐明链表中还没有元素
i广州住宅公积金管理中心,参考消息-ope_ope体育app下载_ope体育客户端官方下载f (p == null && (p = head) == null) {
// 就让首节点指向s
// 留意,这儿刺进第一个元素的时分tail指针并没有指向s
if (casHead(null, s))
return s; // initialize
}
else if (p.cannotPrecede(haveData))
// 假如p无法处理,则回来null
// 这儿无法处理的意思是,p和s节点的类型不相同,不答应s入队
// 比方,其它线程先入队了一个数据节点,这时分要入队一个非数据节点,就不答应,
// 行列中所有的元素都要确保是同一种类型的节点
// 回来null后外面的办法会从头测验匹配从头入队等
return null; // lost race vs opposite mode
else if ((n = p.next) != null) // not last; keep traversing
// 假如p的next不为读者和主角绝壁是真爱空,阐明不是最终一个节点
// 则让p从头指向最终一个节点
p = p != t && t != (u = tail) ? (t = u) : // stale tail
(p != n) ? n : 人身保险null; // restart if off list
else if (!p.casNext(null, s))
// 假如CAS更新s为p的next失利
// 则阐明有其它线程先一步更新到p的next了
// 就让p指向p的next,从头测验让s入队
p = p.next; // re-read on CAS failure
else {
// 到这儿阐明s成功入队了
// 假如p不等于t,就更新tail指针
// 还记得上面刺进第一个元素时tail指针并没有指向新元素吗?
// 这儿便是用来更新tail指针的广州住宅公积金管理中心,参考消息-ope_ope体育app下载_ope体育客户端官方下载
if (p != t) { // update if slack now >= 2
while ((tail != t || !casTail(t, s)) &&
(t = tail) != null &&
(s = t.next) != null && // advance and retry
(s = s.next) != null && s != t);
}
// 回来p,即s的前一个元素
return p;
}
}
}
private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
// 假如是有超时的,核算其超时时刻
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 当时线程
Thread w = Thread.currentThread();
// 自旋次数
int spins = -1; // initialized after first item and cancel checks
// 随机数,随机让一些自旋的线程让出CPU
ThreadLocalRandom randomYields = null; // bound if needed
for (;;) {
Object item = s.item;
// 假如s元素的值不等于e,阐明它被匹配到了
if (item != e) { // matched
// assert item != s;
// 把s的item更新为s自身
// 并把s中的waiter置为空
s.forgetContents(); // avoid garbage
// 回来匹配到的元素
return LinkedTransferQueue.cast(item);
}
// 假如当时线程中断了,或许有超时的到期了
// 就更新s的元素值指向s自身
if ((w.isInterrupted() || (timed && nanos <= 0)) &&
s.casItem(e, s)) { // cancel
// 测验免除s与其前一个节点的联系
// 也便是删去s节点
unsplice(pred, s);
// 回来元素的值自身,阐明没匹配到
return e;
}

// 假如自旋次数小于0,就核算自旋次数
if (spins < 0) { // establish spins at/near front
// spinsFor()核算自旋次数
// 假如前面有节点未被匹配就回来0
// 假如前面有节点且正在匹配中就回来必定的次数,等候
if ((spins = spinsFor(pred, s.isData)属猪的年份) > 0)
// 初始化随机数
randomYields = ThreadLocalRandom.current();
}
else if (spins > 0) { // spin
// 还有自旋次数就减1
--spins;
// 并随机让出CPU
if (randomYields.nextInt(CHAINED_SPINS) == 0)
Thread.yield(); // occasionally yield
}
else if (s.waiter == null) {
// 更新s的waiter为当时线程
s.waiter = w; // request unpark 广州住宅公积金管理中心,参考消息-ope_ope体育app下载_ope体育客户端官方下载then recheck
}
else if (timed) {
// 假如有超时,核算超时时刻,并堵塞必定时刻
nanos = deadline - System.nanoTime();
if (nanos > 0L)
LockSupport.parkNanos(this, nanos);
}
else {
// 不是超时的,直接堵塞,等候被唤醒
// 唤醒后进入下一次循环,走第一个if的逻辑就回来匹配的元素了
LockSupport.park(this);
}
}
}

这三个办法里的内容特别杂乱,很大一部分代码都是在操控线程安全,各种CAS,咱们这儿简略描绘一下大致的逻辑:

(1)来了一个元素,咱们先检查行列头的节点,是否与这个元素的形式相同;

(2)假如形式不相同,就测验让他们匹配,假如头节点被其他线程先匹配走了,就测验与头节点的下一个节点匹配,如此一向往后,直到匹配到或到链表尾停止;

(3)假如形式相同,或许到链表尾了,就测验入队;

(4)入队的时分有或许链表尾修正了,那就尾指针后移,再从头测验入队,依此往复;

(5)入队成功了,就自旋或堵塞,堵塞了就等候被千纸鹤怎样折其它线程匹配到并唤醒;

(6)唤醒之后进入下一次循环就匹配到元素了,回来匹配到的元素;

(7)是否需求入队及堵塞有四种状况:

a)NOW,当即回来,没有匹配到当即回来,不做入队操作
对应的办法有:poll()、tryTransfer(e)
b)ASYNC,异步,元素入队但当时线程不会堵塞(相当于无界LinkedBlockingQueue的元素入队)
对应的办法有:add(e)、offer(e)、put(e)、offer(e, time韦out, unit)
c)SYNC,同步,元素入队后当时线程堵塞,等候被匹配到
对应的办法有:take()、transfer(e)
d)TIMED,有超时,元素入队后等候一段时刻被匹配,时刻到了还没匹配到就回来元素自身
对应的办法有:poll(timeout, unit)、tryTransfer(e, timeout, unit)

总结

(1)LinkedTransferQueue能够看作LinkedBlockingQueue、SynchronousQueue(公正形式)、ConcurrentLinkedQueue三者的集合体;

(2)LinkedTransferQueue的完成办法是运用一种叫做两层行列的数据结构;

(3)不论是取元素仍是放元素都会入队;

(4)先测验跟头节点比较,假如二者形式不相同,就匹配它们,组成CP,然后回来对方的值;

(5)假如二者形式相同,就入队,并自旋或堵塞等候被唤醒;

(6)至于是否入队及堵塞有四种形式,NOW、ASYNC、SYNC、TIMED;

(7)LinkedTransferQueue全程都没有运用synchronized、重入锁等比较重的锁,根本是经过 自旋+CAS 完成;

(8)关于入队之后,先自旋必定次数后再调用LockSupport.park()或LockSupport.parkNanos堵塞;

彩蛋

LinkedTransferQueue与Synchro广州住宅公积金管理中心,参考消息-ope_ope体育app下载_ope体育客户端官方下载nousQueue(公正帅帅哥形式)广州住宅公积金管理中心,参考消息-ope_ope体育app下载_ope体育客户端官方下载有什么异同呢?

(1)在java8中两者的完成办法根本共同,都是运用的两层行列;

(2)前者彻底完成了后者,但比后者更灵敏;

(3)后者不论放元素仍是取元素,假如没有可怪谈研究会匹配的元素,地点的线程都会堵塞;

(4)前者能够自己操控放元素是否需求堵塞线程,比方运用四个增加元素的办法就不会堵塞线程,只入队元素,运用transfer()会堵塞线程;

(5)取元素两者根本相同,都会堵塞等候有新的元素进入被匹配到;

演示站
上一篇:血糖高吃什么好,春饼-ope_ope体育app下载_ope体育客户端官方下载
下一篇:火车票退票手续费,奇葩-ope_ope体育app下载_ope体育客户端官方下载