1、简介
SynchronousQueue 是一个容器大小为0但存储元素的阻塞容器,存储结构为链表;那这里为什么说是牵手容器呢?
这个容器就一个牵桥搭线的地方,为广大的单身同胞元素进行成对匹配,牵手成功后,就双宿双飞了,但是也是有一定规则的
- 匹配只能从排队固定一端进行匹配,排队也必须从固定一端进行
- 元素的操作分为读取、存放;读取-读取 存放-存放为同性, 读取-存放, 存放-读取为异性
- 同性元素进行排队,异性元素进行匹配,匹配成功,则一起牵手离开
- 当多个异性元素来临时,队端的那个元素去领走心中满意的一个异性;若队列中没有,则这些异性元素排队,否则重复此过程
- 排队只是一个宽泛的表示:很多同性元素在等待或者暂时没有异性元素,不等待孤独的走了、等一会走了、等到匹配成功、未成功却由于外力因素被叫走,我都称为排队
这个比喻也基本上说出了这个容器的所有操作;此容器的核心也就是读取、存储;其它容器操作基本是固定的,没有太多意义的;Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。
相比大家心里已经有了大概了,让我从代码的角度再来确认以下这个过程
2、SynchronousQueue 源码
内部使用了Transfer对象来代理实现,因为分为公平模式、非公平模式,也可说使用了策略模式,其本身代码还是十分简单的
2.1 构造器
使用存在两种模式,公平模式、非公平模式,默认使用非公平模式
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
2.2 存取
存取方法使用Transferer对象来完成,也就是代理模式
private transient volatile Transferer<E> transferer;
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
if (transferer.transfer(e, false, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
if (transferer.transfer(e, true, unit.toNanos(timeout)) != null)
return true;
if (!Thread.interrupted())
return false;
throw new InterruptedException();
}
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
return transferer.transfer(e, true, 0) != null;
}
public E take() throws InterruptedException {
E e = transferer.transfer(null, false, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E e = transferer.transfer(null, true, unit.toNanos(timeout));
if (e != null || !Thread.interrupted())
return e;
throw new InterruptedException();
}
public E poll() {
return transferer.transfer(null, true, 0);
}
2.3 其它操作
容器为空,大小为0,不包含任何元素
public boolean isEmpty() {
return true;
}
public int size() {
return 0;
}
public boolean contains(Object o) {
return false;
}
无删除清理动作
public void clear() {
}
public boolean remove(Object o) {
return false;
}
3、非公平模式 TransferStack
采用先进后出的模式;也就是后来排队的有优先牵手的机会
3.1 数据节点 SNode
static final class SNode {
volatile SNode next;
volatile SNode match;
volatile Thread waiter;
Object item;
int mode;
SNode(Object item) {
this.item = item;
}
boolean casNext(SNode cmp, SNode val) {
return cmp == next &&
U.compareAndSwapObject(this, NEXT, cmp, val);
}
boolean tryMatch(SNode s) {
if (match == null &&
U.compareAndSwapObject(this, MATCH, null, s)) {
Thread w = waiter;
if (w != null) {
waiter = null;
LockSupport.unpark(w);
}
return true;
}
return match == s;
}
void tryCancel() {
U.compareAndSwapObject(this, MATCH, null, this);
}
boolean isCancelled() {
return match == this;
}
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long MATCH;
private static final long NEXT;
static {
try {
MATCH = U.objectFieldOffset
(SNode.class.getDeclaredField("match"));
NEXT = U.objectFieldOffset
(SNode.class.getDeclaredField("next"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
}
代码中可以得到信息:
- 单链表结构,包含下个节点信息,牵手成功对象节点信息、节点性别、节点所在线程对象
- 对next、match节点进行原子操作
3.1.1 tryMatch方法
boolean tryMatch(SNode s) {
if (match == null &&
U.compareAndSwapObject(this, MATCH, null, s)) {
Thread w = waiter;
if (w != null) {
waiter = null;
LockSupport.unpark(w);
}
return true;
}
return match == s;
}
如果当前节点未匹配到数据,则原子操作至当前匹配为传参,并释放唤醒节点线程;否则当前节点已经匹配成功且被唤醒,则直接返回是否匹配的为传入参数,这个时候基本返回false,也就是传入参数节点需要重新去匹配
3.1.2 tryCancel方法
void tryCancel() {
U.compareAndSwapObject(this, MATCH, null, this);
}
match指针指向自己;这个的调用场景,是存在超时等待,结束后,自我匹配,然后独自离开
3.2 头指针
volatile SNode head;
boolean casHead(SNode h, SNode nh) {
return h == head &&
U.compareAndSwapObject(this, HEAD, h, nh);
}
头指针实现了原子操作
3.3 节点状态
static final int REQUEST = 0;
static final int DATA = 1;
static final int FULFILLING = 2;
static boolean isFulfilling(int m) { return (m & FULFILLING) != 0; }
有三种状态:读状态、存状态、临时读/写数据状态(为了线程安全的一个临时态)
3.4 shouldSpin方法
boolean shouldSpin(SNode s) {
SNode h = head;
return (h == s || h == null || isFulfilling(h.mode));
}
传参节点为头节点、头节点为空、或者节点状态为读取状态;也就是说如果头数据被置为一个临时态的数据,不需要自旋
3.5 awaitFulfill方法
SNode awaitFulfill(SNode s, boolean timed, long nanos) {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
int spins = shouldSpin(s)
? (timed ? MAX_TIMED_SPINS : MAX_UNTIMED_SPINS)
: 0;
for (;;) {
if (w.isInterrupted())
s.tryCancel();
SNode m = s.match;
if (m != null)
return m;
if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
s.tryCancel();
continue;
}
}
if (spins > 0)
spins = shouldSpin(s) ? (spins - 1) : 0;
else if (s.waiter == null)
s.waiter = w; // establish waiter so can park next iter
else if (!timed)
LockSupport.park(this);
else if (nanos > SPIN_FOR_TIMEOUT_THRESHOLD)
LockSupport.parkNanos(this, nanos);
}
}
大致流程
- 计算等待结束时间、自旋次数
- for循环进行自旋
- 如果s节点线程被打断,则cas操作进行自我牵手
- 如果匹配数据不为空,返回匹配数据,可能是自我牵手离场,也可能是匹配成功,成对离场
- 如果是超时时长结束,则cas操作自我牵手,继续下次循环
- 自旋次数大于0时,减1,继续下次循环
- 如果节点线程对象为空,则置为当前线程
- 如果是无限等待策略,则线程挂起
- 5中超时时长大于某个值时,则超时时长挂起线程,否则等待5计算跳出循环结束
3.6 clean方法
void clean(SNode s) {
s.item = null;
s.waiter = null;
SNode past = s.next;
if (past != null && past.isCancelled())
past = past.next;
SNode p;
while ((p = head) != null && p != past && p.isCancelled())
casHead(p, p.next);
while (p != null && p != past) {
SNode n = p.next;
if (n != null && n.isCancelled())
p.casNext(n, n.next);
else
p = n;
}
}
从代码中可以看出,做了两件事
- 把现有节点链表中,找到第一个不为空,且没有取消的节点做为头节点
- 从当前节点开始,去掉为空节点或者取消节点
3.7 核心方法 transfer
E transfer(E e, boolean timed, long nanos) {
SNode s = null;
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
SNode h = head;
if (h == null || h.mode == mode) {
if (timed && nanos <= 0L) {
if (h != null && h.isCancelled())
casHead(h, h.next);
else
return null;
} else if (casHead(h, s = snode(s, e, h, mode))) {
SNode m = awaitFulfill(s, timed, nanos);
if (m == s) {
clean(s);
return null;
}
if ((h = head) != null && h.next == s)
casHead(h, s.next);
return (E) ((mode == REQUEST) ? m.item : s.item);
}
} else if (!isFulfilling(h.mode)) {
if (h.isCancelled())
casHead(h, h.next);
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
for (;;) {
SNode m = s.next;
if (m == null) {
casHead(s, null);
s = null;
break;
}
SNode mn = m.next;
if (m.tryMatch(s)) {
casHead(s, mn);
return (E) ((mode == REQUEST) ? m.item : s.item);
} else
s.casNext(m, mn);
}
}
} else {
SNode m = h.next;
if (m == null)
casHead(h, null);
else {
SNode mn = m.next;
if (m.tryMatch(h))
casHead(h, mn);
else
h.casNext(m, mn);
}
}
}
}
首先对参数进行说明:
- E e 数据,如果请求则为null,如果插入则为要插入数据
- boolean timed,超时规则,如果false,则无限等待,否则有限时间内等待
- long nanos,有限等待时长,若小于等于0表示不等待
分为以下情况处理:整个处理是一个for自旋操作,保证cas操作的失败(数据已经被其它线程操作破坏)时,重头处理
- 栈中无数据,或者栈头数据类别和当前数据模式一致
- 不等待时,检查栈头是否为有效,无效则重置头,重新for循环处理,有效则返回null
- 生成节点,并且cas操作置为栈头:失败,则表示栈头发生变化,重新for自旋,否则使用方法awaitFulfill 等待查找匹配对象;匹配对象等于自己,则使用clean清理数据,并返回null;cas操作头节点为当前节点的下个节点,返回数据
- 栈头数据类别和当前数据不一致,且栈头状态不是临时态
- 首先对栈头状态判断,无效则往后cas置换;
- 生成临时读/写状态节点(为啥是临时读写呢,因为它和栈头节点可以牵手成功,根根不用入栈的),并cas操作置为头节点;cas失败,则重新for自旋,否则增加一个内部for自旋,a)判断状态当前节点后无节点,跳出内循环,从外循环重新开始,b) 尝试和下个节点匹配,匹配成功,则返回,c) 失败,则把节点重置为下下个节点,重新内循环(原理:栈里等待全部与新节点异性,总会能找到一个,如果栈内异性节点都没有了,按照a跳出内循环)
- 栈头数据状态为临时态
- 说明栈头节点的下一个节点已经有匹配对象,这时,则需要帮助它们牵手离开;如果栈头的下个节点为空,则栈头cas置换为null;否则cas尝试牵手,成功则重置栈头为栈头下下个节点,否则栈头的下个节点已经有匹配的了,则把栈头next节点置为栈头next-next节点
4、公平模式 TransferQueue
和非公平类似,但又有不同,不同点:从链表尾部插入节点等待,从链表头取出数据匹配
4.1 节点QNode
static final class QNode {
volatile QNode next;
volatile Object item;
volatile Thread waiter;
final boolean isData;
QNode(Object item, boolean isData) {
this.item = item;
this.isData = isData;
}
boolean casNext(QNode cmp, QNode val) {
return next == cmp &&
U.compareAndSwapObject(this, NEXT, cmp, val);
}
boolean casItem(Object cmp, Object val) {
return item == cmp &&
U.compareAndSwapObject(this, ITEM, cmp, val);
}
void tryCancel(Object cmp) {
U.compareAndSwapObject(this, ITEM, cmp, this);
}
boolean isCancelled() {
return item == this;
}
boolean isOffList() {
return next == this;
}
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long ITEM;
private static final long NEXT;
static {
try {
ITEM = U.objectFieldOffset
(QNode.class.getDeclaredField("item"));
NEXT = U.objectFieldOffset
(QNode.class.getDeclaredField("next"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
}
实现了cas原子操作,使用item保存当前数据,和匹配数据,isData标志读/写数据
4.2 成员变量
transient volatile QNode head;
transient volatile QNode tail;
transient volatile QNode cleanMe;
头节点、尾节点,待清理节点
4.3 awaitFulfill方法
内容和非公平模式基本一样了,就不分析了;但由于数据结构不同,在判断item是否为匹配数据变化为:如果item数据不为节点的数据,则为匹配数据
Object x = s.item;
if (x != e)
return x;
4.4 clean方法
void clean(QNode pred, QNode s) {
s.waiter = null;
while (pred.next == s) {
QNode h = head;
QNode hn = h.next;
if (hn != null && hn.isCancelled()) {
advanceHead(h, hn);
continue;
}
QNode t = tail;
if (t == h)
return;
QNode tn = t.next;
if (t != tail)
continue;
if (tn != null) {
advanceTail(t, tn);
continue;
}
if (s != t) {
QNode sn = s.next;
if (sn == s || pred.casNext(s, sn))
return;
}
QNode dp = cleanMe;
if (dp != null) {
QNode d = dp.next;
QNode dn;
if (d == null ||
d == dp ||
!d.isCancelled() ||
(d != t &&
(dn = d.next) != null &&
dn != d &&
dp.casNext(d, dn)))
casCleanMe(dp, null);
if (dp == pred)
return;
} else if (casCleanMe(null, pred))
return;
}
}
- 如果已经清理成功,则不做任何其它处理
- 检查队列头,无效,则找到一个有效的队列头(不为空,且未取消)重新开始;
- 如果头尾相同,结束
- 检查尾部节点使其有效;无效则重新开始
- 当前待清理节点为空,则把传入参数作为待清理节点,重新开始
- 当前待清理节点不为空,则进行清理操作,如果操作成功,则把待清理节点置空,如果待清理节点,确实为清理节点,则返回,否则重新开始
4.5 核心方法 transfer
E transfer(E e, boolean timed, long nanos) {
QNode s = null;
boolean isData = (e != null);
for (;;) {
QNode t = tail;
QNode h = head;
if (t == null || h == null)
continue;
if (h == t || t.isData == isData) {
QNode tn = t.next;
if (t != tail)
continue;
if (tn != null) {
advanceTail(t, tn);
continue;
}
if (timed && nanos <= 0L)
return null;
if (s == null)
s = new QNode(e, isData);
if (!t.casNext(null, s))
continue;
advanceTail(t, s);
Object x = awaitFulfill(s, e, timed, nanos);
if (x == s) {
clean(t, s);
return null;
}
if (!s.isOffList()) {
advanceHead(t, s);
if (x != null)
s.item = s;
s.waiter = null;
}
return (x != null) ? (E)x : e;
} else {
QNode m = h.next;
if (t != tail || m == null || h != head)
continue;
Object x = m.item;
if (isData == (x != null) ||
x == m ||
!m.casItem(x, e)) {
advanceHead(h, m);
continue;
}
advanceHead(h, m);
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
}
}
for循环自旋,流程有两种情况:
- 存在异性节点
- 检查数据节点是否发生变化,发生变换重新for循环
- 检查数据状态和匹配数据是否一致,匹配数据是否为取消数据,重置节点数据失败,则加入对头,重新处理
- 这是匹配数据是有效的,唤醒匹配的线程,返回数据
- 仅仅存在同性节点
- 检查尾节点状态,有效且真的是尾节点
- 如果不等待,则返回null
- 生成新节点,并连接为队尾的下个节点
- 移动队尾节点为新节点
- awaitFulfill 等待返回匹配数据
- 匹配节点为自身,则清理当前节点,返回null
- 如果队列中数据存在,则找到下个可能有效的头部
- 返回数据
5、结语
这是一个巧妙的算法实现,也可以说是一种“生产者-消费者模式”,只不过是针对线程任务的容器;只不过生产的东西必须等待消费,或者自行离开;使用自旋+原子操作+线程挂起唤醒,减少线程挂起唤醒的资源消耗;非公平模式相对于公平模式,匹配均在栈头,增加线程同步的复杂度;暂时只有线程池中,创建仅仅有缓存线程时,有使用场景,为了有任务就可创建线程来执行;
自旋+原子操作,保证同步时,总是在进行cas操作时,进行状态的重新检查;以保证操作的基础是正确的,不正确则需要从头开始了
技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!