面试官:好了,你也休息了十分钟了,咱们接着往下聊聊SynchronousQueue的非公平模式吧。

Hydra:好的,有了前面公平模式的基础,非公平模式理解起来就非常简单了。公平模式下,SynchronousQueue底层使用的是TransferQueue,是一个先进先出的队列,而非公平模式与它不同,底层采用了后进先出的TransferStack栈来实现。

下面我们还是先写一个例子来看看效果,首先创建3个线程使用put方法向SynchronousQueue中插入数据,结束后再使用3个线程调用take方法:

SynchronousQueue<Integer> queue=new SynchronousQueue<>(false);

@AllArgsConstructor
class PutThread implements Runnable{
int i;
@SneakyThrows
@Override
public void run() {
queue.put(i);
System.out.println("putThread "+i+" end");
}
}
class TakeThread implements Runnable{
@SneakyThrows
@Override
public void run() {
System.out.println("takeThread take: "+queue.take());
}
} for (int i = 1; i <=3; i++) {
new Thread(new PutThread(i)).start();
Thread.sleep(1000);
}
for (int i = 1; i <=3 ; i++) {
new Thread(new TakeThread()).start();
Thread.sleep(1000);
}

运行上面的代码,查看结果:

takeThread take: 3
putThread 3 end
takeThread take: 2
putThread 2 end
takeThread take: 1
putThread 1 end

可以看到,生产者线程在执行完put后会进行阻塞,直到有消费者线程调用take方法取走了数据,才会唤醒被阻塞的线程。并且,数据的出队与入队顺序是相反的,即非公平模式下采用的是后进先出的顺序。

面试官:就是把结构从队列换成了栈,真就这么简单?

Hydra:并不是,包括底层节点以及出入栈的逻辑都做了相应的改变。我们先看节点,在之前的公平模式中队列的节点是QNode,非公平模式下栈中节点是SNode,定义如下:

volatile SNode next; // 指向下一个节点的指针
volatile SNode match; // 存放和它进行匹配的节点
volatile Thread waiter; // 保存阻塞的线程
Object item;
int mode;
SNode(Object item) {
this.item = item;
}

QNode类似,如果是生产者构建的节点,那么item非空,如果是消费者产生的节点,那么itemnull。此外还有一个mode属性用来表示节点的状态,它使用TransferStack中定义的3个常量来表示不同状态:

static final int REQUEST    = 0; //消费者
static final int DATA = 1; //生产者
static final int FULFILLING = 2; //匹配中状态

TransferStack中没有携带参数的构造函数,使用一个head节点来标记栈顶节点:

volatile SNode head;

面试官:基本结构就讲到这吧,还是老规矩,先从入队操作开始分析吧。

Hydra:当栈为空、或栈顶元素的类型与自己相同时,会先创建一个SNode节点,并将它的next节点指向当前栈顶的head,然后将head指针指向自己。这个过程中通过使用CAS保证线程安全,如果失败则退出,在循环中采取自旋的方式不断进行尝试,直到节点入栈成功。用一张图来表示两个线程同时入栈的场景:

当节点完成入栈后,调用awaitFulfill方法,等待匹配的操作的到来。在这一过程中,会使节点对应的线程进行自旋或挂起操作,直到匹配操作的节点将自己唤醒,或被其他线程中断、等待超时。

当入栈后的节点是栈顶节点,或者节点的类型为FULFILLING匹配状态时,那么可能会马上完成匹配,因此先进行自旋,当超过自旋次数上限后再挂起。而如果节点在自旋过程中,有新的节点压入栈顶,会将非栈顶节点剩余的自旋次数直接清零,挂起线程避免浪费资源。

面试官:你上面也说了,挂起的线程有可能会超时或者被中断,这时候应该怎么处理?

Hydra:当这两种情况出现时,SNode会将match属性设为自身,退出awaitFulfill方法,然后调用clean方法将对应的节点清理出栈。具体情形可分为两种情况。先说简单的情况,如果清理的是栈顶节点,那么直接将head节点指向它的next节点,即将当前栈顶结点弹出即可。

面试官:那么如果要删除的节点不是栈顶的节点呢?

Hydra:如果清理的不是栈顶节点,会稍微有一些麻烦。因为栈的底层是一个单向的链表结构,所以需要从栈顶head节点开始遍历,遍历到被删除节点的后继节点为止。所以在清除工作开始前,先使用了一个past节点标记需要删除节点的下一个节点,作为结束遍历的标记。

然后创建一个标记节点p,初始时指向head结点,开始循环,如果pnext节点不是需要被删除的节点,那么就将p向后移一个位置,直到找到这个需要被删除的中断或超时的节点,然后将pnext指向这个删除节点的next节点,在逻辑上完成链表中节点的删除。

面试官:单一类型节点的入栈应该说完了吧,接下来说说不同类型节点间是如何实现的匹配操作吧?

Hydra:好的,那我们先回顾一点上面的知识,前面说过每个节点有一个mode属性代表它的模式,REQUEST表示它是消费者,DATA表示是生产者,FULFILLING表明正处于匹配中的状态。

在一个新的线程调用方法时,先判断它的类型mode是什么,如果和当前栈顶head节点类型不同,且head节点的状态不为匹配中时,将它的状态设置为FULFILLING|mode,压入栈中。然后将尝试匹配新的head节点和它的next节点,如果匹配成功,会将next节点的match属性设置为head节点,唤醒挂起的next节点中的线程。

在完成匹配后,当前头结点对应的线程会协助推进head节点,将head指向next节点的下一个节点,即完成了栈顶两节点的出栈。最终消费者线程会返回匹配的生产者节点中的item数据值,而生产者线程也会结束运行退出。

我们以栈中当前节点为DATA类型,新节点为REQUEST类型画一张图,来直观的感受一下上面的流程:

面试官:总算是讲完了,能对SynchronousQueue做一个简单的总结吗?

Hydra:SynchronousQueue基于底层结构,实现了线程配对通信这一机制。在它的公平模式下使用的是先进先出(FIFO)的队列,非公平模式下使用的是后进先出(LIFO)的栈,并且SynchronousQueue没有使用synchronizedReentrantLock,而是使用了大量的CAS操作来保证并发操作。可能我们在平常的工作中使用场景不是很多,但是在线程池的设计中使用了SynchronousQueue,还是有很重要的应用场景的。

面试官:讲的还行,不过刚才这些和公平模式听起来感觉区别不大啊,没有什么技术含量。这样吧,你明天过来我们加试一场,我再给你打分。

Hydra:(溜了溜了,还是找家别的靠谱公司吧……)

最后

如果觉得对您有所帮助,小伙伴们可以点赞、转发一下~非常感谢

微信搜索:码农参上,来加个好友,点赞之交也好啊~

公众号后台回复“面试”、“导图”、“架构”、“实战”,获得免费资料哦~

面试侃集合 | SynchronousQueue非公平模式篇的更多相关文章

  1. 面试侃集合 | SynchronousQueue公平模式篇

    面试官:呦,小伙子来的挺早啊! Hydra:那是,不能让您等太久了啊(别废话了快开始吧,还赶着去下一场呢). 面试官:前面两轮表现还不错,那我们今天继续说说队列中的SynchronousQueue吧. ...

  2. 图解SynchronousQueue原理详解-非公平模式

    SynchronousQueue原理详解-非公平模式 开篇 说明:本文分析采用的是jdk1.8 约定:下面内容中Ref-xxx代表的是引用地址,引用对应的节点 前面已经讲解了公平模式的内容,今天来讲解 ...

  3. 面试侃集合 | ArrayBlockingQueue篇

    面试官:平常在工作中你都用过什么什么集合? Hydra:用过 ArrayList.HashMap,呃-没有了 面试官:好的,回家等通知吧- 不知道大家在面试中是否也有过这样的经历,工作中仅仅用过的那么 ...

  4. 图解SynchronousQueue原理-公平模式

    SynchronousQueue原理详解-公平模式 一.介绍 SynchronousQueue是一个双栈双队列算法,无空间的队列或栈,任何一个对SynchronousQueue写需要等到一个对Sync ...

  5. 面试侃集合 | LinkedBlockingQueue篇

    面试官:好了,聊完了ArrayBlockingQueue,我们接着说说LinkedBlockingQueue吧 Hydra:还真是不给人喘口气的机会,LinkedBlockingQueue是一个基于链 ...

  6. 面试侃集合 | DelayQueue篇

    面试官:好久不见啊,上次我们聊完了PriorityBlockingQueue,今天我们再来聊聊和它相关的DelayQueue吧. Hydra:就知道你前面肯定给我挖了坑,DelayQueue也是一个无 ...

  7. 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁

    问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...

  8. 图解AQS原理之ReentrantLock详解-非公平锁

    概述 并发编程中,ReentrantLock的使用是比较多的,包括之前讲的LinkedBlockingQueue和ArrayBlockQueue的内部都是使用的ReentrantLock,谈到它又不能 ...

  9. 【JAVA秒会技术之秒杀面试官】秒杀Java面试官——集合篇(一)

    [JAVA秒会技术之秒杀面试官]秒杀Java面试官——集合篇(一) [JAVA秒会技术之秒杀面试官]JavaEE常见面试题(三) http://blog.csdn.net/qq296398300/ar ...

随机推荐

  1. 聊一聊Jmeter的参数化

    背景 前面一篇讲了 JMeter 的一个最简单的例子,这篇聊一下 JMeter 的参数化. 在开始之前先来一个单元测试的例子,感受一下参数化. 上面是一个用 xUnit 写的单元测试,这个单元测试就是 ...

  2. 粗浅聊聊Python装饰器

    浅析装饰器 通常情况下,给一个对象添加新功能有三种方式: 直接给对象所属的类添加方法: 使用组合:(在新类中创建原有类的对象,重复利用已有类的功能) 使用继承:(可以使用现有类的,无需重复编写原有类进 ...

  3. day-25-类的继承顺序-父类对子类的约束-多态-队列和栈

    一.类的继承顺序 只要继承object类就是新式类 不继承object类的都是经典类 在python3 中所有的类都继承object类,都是新式类 在python2 中不继承object的类都是经典类 ...

  4. 【策略模式】策略模式结合Spring实战Demo

    策略模式 策略模式用于解决判断分支过多的问题,使代码变得简洁和优雅, 策略模式在多种方式在项目中落地,下面举例说明通过指定不同类型的订单使用策略模式执行不同的业务逻辑 文章参考自公众号:石杉的架构笔记 ...

  5. hdu3594 强连通 tarjan

    题意: 判断是不是强连通图 ,同时每一条边必须只能在一个环里 思路:之前我的强连通用的全是双深搜,结果题目的第二个要求很难判断,一开始写了三个深搜加上并查集,结果越写越乱,其实就是在判断一个边是否只在 ...

  6. Windows核心编程 第26章 窗口消 息

    窗 口 消 息 Wi n d o w s允许一个进程至多建立10 000个不同类型的用户对象(User object):图符.光标.窗口类.菜单.加速键表等等.当一个线程调用一个函数来建立某个对象时, ...

  7. Open VAS 漏扫工具的安装

    wget -q -O - http://www.atomicorp.com/installers/atomic |sh .安装openvas [root@localhost ~]#yum -y ins ...

  8. [CTF]摩斯电码

    摩尔斯电码 -----------转载 https://morse.supfree.net/ 摩尔斯电码定义了包括:英文字母A-Z(无大小写区分)十进制数字0-9,以及"?"&qu ...

  9. Yii2表头强制不换行

    在Yii2中使用GridView生成的表格有一个小问题,那就是表头的列宽是根据列的值自适应的,但有时值的长度比较小,表头就会出现下面这种情况(表头不在同一行,非常的不利于数据的查看) 设置强制不换行可 ...

  10. 查询某软件所连接的外网IP地址

    一:背景环境: 1>:某机械公司用的某些特殊软件,需要实现所有使用某软件的屏蔽其软件所连接的外网ip,其他上网功能不做限制. 二:需求分析:可以查出此软件所连接的外网ip,在路由器的ip过滤中将 ...