LinkedTransferQueue
LinkedTransferQueue 链表实现的无界阻塞队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。其使用一种预占的模式,当消费者获取元素的时候如果队列不为空则直接获取元素,如果队列为空,则生产一个null的元素,然后在这个节点上等待。
当有新的线程添加元素的时候,如果发现有null的元素则,直接唤醒消费的线程取走元素。
java实现原理
public class LinkedTransferQueue<E> extends AbstractQueue<E>
implements TransferQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -3223113410248163686L;
// 返回到Java虚拟机的可用的处理器数量
private static final boolean MP =
Runtime.getRuntime().availableProcessors() > 1;
// 等待节点在阻塞之前自旋的次数 128
private static final int FRONT_SPINS = 1 << 7;
// 当前置节点正在处理,当前节点在阻塞之前的自选次数 64
private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;
static final int SWEEP_THRESHOLD = 32;
static final class Node {
// 如果是生产的数据则为true,如果是消费线程请求的节点则false
final boolean isData; // false if this is a request node
// 数据内容,如果是消费线程生成的元素则为null
volatile Object item; // initially non-null if isData; CASed to match
// 下一个节点
volatile Node next;
// 此节点上等待的线程
volatile Thread waiter; // null until waiting
// CAS方式设置next
// CAS methods for fields
final boolean casNext(Node cmp, Node val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
// CAS方式设置元素
final boolean casItem(Object cmp, Object val) {
// assert cmp == null || cmp.getClass() != Node.class;
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
/**
* Constructs a new node. Uses relaxed write because item can
* only be seen after publication via casNext.
*/
Node(Object item, boolean isData) {
UNSAFE.putObject(this, itemOffset, item); // relaxed write
this.isData = isData;
}
// 取消下一节点连接,next指向自己
final void forgetNext() {
UNSAFE.putObject(this, nextOffset, this);
}
// 设置item自连接,waiter为null
final void forgetContents() {
UNSAFE.putObject(this, itemOffset, this);
UNSAFE.putObject(this, waiterOffset, null);
}
// 是否被匹配过
final boolean isMatched() {
Object x = item;
return (x == this) || ((x == null) == isData);
}
// 是否是一个未匹配的请求节点
// 主要是根据isData和元素内容判断
final boolean isUnmatchedRequest() {
return !isData && item == null;
}
final boolean cannotPrecede(boolean haveData) {
boolean d = isData;
Object x;
return d != haveData && (x = item) != this && (x != null) == d;
}
// 匹配数据节点
final boolean tryMatchData() {
// assert isData;
Object x = item;
if (x != null && x != this && casItem(x, null)) {
LockSupport.unpark(waiter);
return true;
}
return false;
}
private static final long serialVersionUID = -3375979862319811754L;
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long itemOffset;
private static final long nextOffset;
private static final long waiterOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = Node.class;
itemOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("item"));
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
waiterOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("waiter"));
} catch (Exception e) {
throw new Error(e);
}
}
}
// 头部节点
/** head of the queue; null until first enqueue */
transient volatile Node head;
// 尾部节点
/** tail of the queue; null until first append */
private transient volatile Node tail;
// 剥离已删除节点的明显失败数
/** The number of apparent failures to unsplice removed nodes */
private transient volatile int sweepVotes;
}
其内部类似LinkedBlockingQueue内部维持了Node类型的对象作为队列的元素。不同之处Node除了正常维护数据和元素关系之外,还会维护一个等待线程。这也是为了预占的模式下保存等待的线程而设计的。
基本操作
入队
LinkedTransferQueue入队的方法主要有add、put、offer三种。
public boolean add(E e) {
xfer(e, true, ASYNC, 0);
return true;
}
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;
}
但是LinkedTransferQueue除了提供传统Queue的入队方法还挺了tryTransfer
和transfer
两种入队方式
// 如果可能,立即将元素转移给等待的消费者。
// 更确切地说,如果存在消费者已经等待接收它(在 take 或 timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则返回 false。
boolean tryTransfer(E e);
// 将元素转移给消费者,如果需要的话等待。
// 更准确地说,如果存在一个消费者已经等待接收它(在 take 或timed poll(long,TimeUnit)poll)中,则立即传送指定的元素,否则等待直到元素由消费者接收。
void transfer(E e) throws InterruptedException;
出队
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() {
return xfer(null, false, NOW, 0);
}
这里可以看到,几种方法最终都是使用xfer
进行入队和出队操作的,区别是主要传递的数据模式不同。这里主要介绍下xfer
xfer
从上面我们可以看到两种数据操作模式对应的参数
private E xfer(E e, boolean haveData, int how, long nanos) {
// 当入队操作haveData=true的时候,插入元素不能为空
if (haveData && (e == null))
throw new NullPointerException();
// 插入节点
Node s = null; // the node to append, if needed
// 设置起始点
retry:
for (;;) { // restart on append race
// 这一部分主要任务为 发现是否有需要匹配的消费节点
// 从队首开始匹配
for (Node h = head, p = h; p != null;) { // find & match first node
boolean isData = p.isData;
Object item = p.item;
// 判断节点是否被匹配
// 当一个节点的元素不为自己
// (item != null) == isData 主要有两种
// item为null,isDate = false标识其为消费线程创建的节点,并且没有被匹配的值
// item不为null,isData = true标识其为入队操作
if (item != p && (item != null) == isData) { // unmatched
// 是否被操作匹配
// 比如入队的时候haveData=true,此时节点为数据节点而操作为入队操作,则不需要下面的匹配工作
// 如果此节点为消费节点,而此节点为入队操作,或者此节点为数据节点,此时操作为出队操作,则继续下面操作
if (isData == haveData) // can't match
break;
// 通过CAS方式设置节点来判断是否可以匹配
if (p.casItem(item, e)) { // match
for (Node q = p; q != h;) {
Node n = q.next; // update by 2 unless singleton
// 更新head为匹配节点的next节点
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
}
// 匹配成功,则唤醒阻塞的线程
LockSupport.unpark(p.waiter);
// 类型转换,返回匹配节点的元素
return LinkedTransferQueue.<E>cast(item);
}
}
// 若节点已经被匹配过了,则向后寻找下一个未被匹配的节点
Node n = p.next;
// 当前节点已经断开链接则从head开始寻找
p = (p != n) ? n : (h = head); // Use head if p offlist
}
// 没有匹配到等待的节点后,将当前元素加入队尾
if (how != NOW) { // No matches available
if (s == null)
s = new Node(e, haveData);
// 将新节点s添加到队列尾并返回s的前驱节点
Node pred = tryAppend(s, haveData);
// 前驱节点为null,说明有其他线程竞争,并修改了队列,则从retry重新开始
if (pred == null)
continue retry; // lost race vs opposite mode
// 不为ASYNC方法,则同步阻塞等待
if (how != ASYNC)
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
return e; // not waiting
}
}
xfer
- 从head遍历寻找未被匹配的节点,找到一个未被匹配且数据操作模式和本次操作符合的节点
- 匹配节点成功就通过CAS 操作将匹配节点的item字段设置为e,若修改失败,则继续向后寻找节点。
- 通过CAS操作更新head节点为匹配节点的next节点,旧head节点进行自连接,唤醒匹配节点的等待线程waiter,返回匹配的 item。
node可能存在的状态
Node存在的状态主要两个字段来维护,一个是isData判断是否数据节点还是消费节点,item来表示数据是否被匹配到。
状态描述 | isData | item |
---|---|---|
数据节点-匹配前 | true | item |
数据节点-匹配后 | true | null |
消费节点-匹配前 | false | null |
消费节点-匹配后 | false | this |
可以看到整个方法根据数据模式被设置为入队和出队两模式。主要是根据要插入队列的元素e以及haveData参数值来判断数据
入队操作 | e | haveData | how | nanos |
---|---|---|---|---|
入队操作 | 非null | true | ASYNC | 0 |
出队操作-阻塞 | null | false | SYNC | 0 |
出队操作-非阻塞 | null | false | TIMED | unit.toNanos(timeout) |
出队操作-超时 | null | false | NOW | 0 |
how
参数的作用
NOW:立即返回,也不会插入节点
SYNC:插入元素到队列的尾部,然后自旋或阻塞当前线程直到节点被匹配或者取消。
ASYNC:插入元素到队列的尾部,不阻塞直接返回。
TIMED:插入元素到队列的尾部,然后自旋或阻塞当前线程直到节点被匹配或者取消或者超时。
tryAppend
LinkedTransferQueue 在没有完成消费节点匹配后,会将元素添加到队列中,此时使用tryAppend
的方法
private Node tryAppend(Node s, boolean haveData) {
// 从尾部开始进行元素插入
for (Node t = tail, p = t;;) { // move p to last node and append
Node n, u; // temps for reads of next & tail
// 空的队列,则设置节点为head节点返回
if (p == null && (p = head) == null) {
if (casHead(null, s))
return s; // initialize
}
// 如果具有给定模式的节点无法附加到此节点,则返回true,因为此节点不匹配并且具有相反的数据模式。
// 此时为相反模式
else if (p.cannotPrecede(haveData))
return null; // lost race vs opposite mode
// 非最后一个节点,继续遍历
else if ((n = p.next) != null) // not last; keep traversing
p = p != t && t != (u = tail) ? (t = u) : // stale tail
(p != n) ? n : null; // restart if off list
// 尝试CAS设置下一节点失败
else if (!p.casNext(null, s))
p = p.next; // re-read on CAS failure
else {
// 非自连接
if (p != t) { // update if slack now >= 2
// (tail != t || !casTail(t, s) t不为尾部节点,或者 使用t和s设置尾部节点失败
//
while ((tail != t || !casTail(t, s)) &&
(t = tail) != null &&
(s = t.next) != null && // advance and retry
(s = s.next) != null && s != t);
}
return p;
}
}
}
awaitMatch
加入队列后,如果how没有设置非阻塞则调用awaitMatch()方法阻塞等待:
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
ThreadLocalRandom randomYields = null; // bound if needed
// 循环
for (;;) {
Object item = s.item;
// 当前节点已经被匹配
if (item != e) { // matched
// assert item != s;
// 取消节点,并返回匹配值
s.forgetContents(); // avoid garbage
return LinkedTransferQueue.<E>cast(item);
}
// 线程中断或者超时,则将s的节点item设置为s
if ((w.isInterrupted() || (timed && nanos <= 0)) &&
s.casItem(e, s)) { // cancel
// 断开节点
unsplice(pred, s);
return e;
}
// 自旋开始
if (spins < 0) { // establish spins at/near front
// 计算次数
if ((spins = spinsFor(pred, s.isData)) > 0)
randomYields = ThreadLocalRandom.current();
}
else if (spins > 0) { // spin
--spins;
// 生成随机数来让出CPU时间
if (randomYields.nextInt(CHAINED_SPINS) == 0)
Thread.yield(); // occasionally yield
}
// 将s的waiter设置为当前线程
else if (s.waiter == null) {
s.waiter = w; // request unpark then recheck
}
// 超时阻塞
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos > 0L)
LockSupport.parkNanos(this, nanos);
}
// 非超时阻塞
else {
LockSupport.park(this);
}
}
}
操作流程
整个流程可以用下面内容描述
关于LinkedTransferQueue
LinkedTransferQueue使用了类似LinkedBlockingQueue的链表操作,但是并没有使用锁进行操作,整体性能比LinkedBlockingQueue要好。
其使用一种预占的模式,为队列数据获取提供了一种高效的解决方案。
个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,假如开发同学发现了,请及时告知,我会第一时间修改相关内容。假如我的这篇内容对你有任何帮助的话,麻烦给我点一个赞。你的点赞就是我前进的动力。