netty5笔记ITeye - 亚美娱乐

netty5笔记ITeye

2019-01-12 06:53:45 | 作者: 语海 | 标签: 节点,此刻,一个 | 浏览: 1187

NioEventLoop里边运用了MpscLinkedQueue作为taskQueue,替换了父类中默许的LinkedBlockingQueue行列。taskQueue首要用于寄存可履行使命,其调用的频率十分高,因而运用一个更高效的行列能带来很大的收益。 为什么在NioEvnetLoop里用MpscLinkedQueue替换了LinkedBlockingQueue,是运用了更好的算法?仍是通过放弃一些功用的情况来到达更高效的意图?

咱们知道LinkedBlockingQueue也是一个高效的线程安全的行列,它运用了takeLock和putLock两个锁别离作用与消费线程和出产线程,避免了顾客和出产者直接的竞赛。但是在顾客之间、出产者之间仍然需求竞赛各自端的锁。 关于NioEventLoop来说,taskQueue只要一个顾客,即运转NioEventLoop.run()的那个线程(假如不理解这句话,能够看前一篇NioEventLoop的介绍)。也就是说消费端不需求锁, 那是不是咱们去掉消费端那把锁就能进步功率了呢? 并不是!! 在只要一个顾客的情况下,获取锁的时分因为没有竞赛,一个cas就能完结确定,功率实极高的。因而只去掉消费端的锁,关于单顾客的场景是没有功率的进步的。 那么? 莫非要把出产者的锁也去掉?!! netty能够通知你,是的。去掉takeLock,去掉puLock,让功率飞一会。。。还有一个要害是,代码简略的你想哭!!!

好了,咱们来看看MpscLinkedQueue是怎样在单顾客多出产者的场景下去锁的。

首要,我决议把MpscLinkedQueue类的注释翻译一下,便当你有一个开端的了解,避免你没看完直接跑去实践成果发现有问题。

1、一个无锁的支撑单顾客多出产者的并发行列;

2、 答应多个出产者一起进行以下操作:offer(Object),add(Object),addAll(Collection);

3、只答应一个顾客进行以下操作:poll(),remove(),remove(Object),clear();

4、以下办法不支撑:remove(Object o),removeAll(Collection),retainAll(Collection);为啥不支撑这三个办法? 看了后边的源码剖析,或许你就知道答案了。

首要MpscLinkedQueue里边有一堆不可思议的字段,这个是用来消除伪同享的(想了解伪同享,能够看看这篇文章),直接忽视。

long p00, p01, p02, p03, p04, p05, p06, p07;
long p30, p31, p32, p33, p34, p35, p36, p37;

实际上的字段就两个headRef、tailRef,表明行列的头节点和尾节点,结构包括两个字段next和value, 通过这两个字段终究可构成一个单项链表:

能够看到,头结点是不存数据的,尾节点的next也是空的。初始化的时分头尾节点相同,且不包括数据。此刻,headRef==tailRef且headRef.next = null

 

 MpscLinkedQueue() {
 MpscLinkedQueueNode E tombstone = new DefaultNode E (null);
 setHeadRef(tombstone);
 setTailRef(tombstone);
 }

出产者添加数据运用add或offer,两者作用相同。

 

 public boolean offer(E value) {
 if (value == null) {
 throw new NullPointerException("value");
 // 假如传入的是node则直接运用,不然实例化一个newTail
 final MpscLinkedQueueNode E newTail;
 if (value instanceof MpscLinkedQueueNode) {
 newTail = (MpscLinkedQueueNode E ) value;
 newTail.setNext(null);
 } else {
 newTail = new DefaultNode E (value);
 // 更新尾节点为newTail,并获取被替换的原节点
 MpscLinkedQueueNode E oldTail = getAndSetTailRef(newTail);
 oldTail.setNext(newTail);
 return true;
 protected final MpscLinkedQueueNode E getAndSetTailRef(MpscLinkedQueueNode E tailRef) {
 // LOCK XCHG in JDK8, a CAS loop in JDK 7/6
 return (MpscLinkedQueueNode E ) UPDATER.getAndSet(this, tailRef);

offer代码就这几行,是不是简略到哭。首要代码就两行:

1、UPDATER.getAndSet(this, tailRef);

UPDATER是tailRef的一个AtomicReferenceFieldUpdater,能够原子的将tailRef修正,并回来修正前的数据;

假定有N(N 0)个线程一起一起添加一条数据,则或许构成下图中左面的情况,此刻局面尽管紊乱,但是你会发现除了本来的oldTail和终究一个newTailN,其他的节点都会呈现两次,并且一次作为new节点(刚被刺进时),一次作为old节点(刺进了其他新节点)。

2、oldTail.setNext(newTail);

而当履行到这句时,各个节点开端通过next进行衔接。因为old和new时一一对应的联系,不存在竞赛,所以终究能够很简略的构成下图中右边的情况(留意实际情况中是没有右下图第二排那个newTail0的情况的,因为它和第一排的newTail0是同一个目标,这儿仅仅为了和左面比照。 别的虚线箭头表明中心或许还有N(N =0)个节点。

顾客消费数据运用poll办法:

 

 // 该办法获取链表中的第一个元素
 private MpscLinkedQueueNode E peekNode() {
 MpscLinkedQueueNode E head = headRef();
 MpscLinkedQueueNode E next = head.next();
 // 头结点与尾节点相同,阐明还在初始化情况,直接回来null(上面有讲过初始情况headRef.next=null)
 if (next == null head != tailRef()) {
 // 当头结点与尾节点不一起,阐明必定现已有数据刺进了;
 // 此刻假如行列还在上图左面的情况,则next == null。
 // 因为oldTail.setNext(newTail)很快就会履行,因而此处直接不断的循环获取next
 do {
 next = head.next();
 } while (next == null);
 return next;
 public E poll() {
 final MpscLinkedQueueNode E next = peekNode();
 if (next == null) {
 return null;
 // next becomes a new head.
 MpscLinkedQueueNode E oldHead = headRef();
 // 直接将此次获取到的数据修正成头结点
 lazySetHeadRef(next);
 // 将原头结点的next置为null,去除oldHead与新头结点之间的相关
 oldHead.unlink();
 // 获取节点中的数据,并将value置为null,去除节点与数据直接的相关
 return next.clearMaybe();
 }

咱们用一张图来看看整个流程:

1、初始情况,头结点尾节点都为default,此刻poll回来null;

2、出产者刺进value0,并替换到tail节点,此刻default.next = value0, value0.next = null,此刻顾客能够poll;

3、出产者刺进更多的value,构成第三行的链表,此刻default.next = value0, value0.next = value1, valueN.next = null,此刻顾客能够poll;

4、顾客消费掉第一个节点的数据,第一个节点变为头结点,此刻value0.value = null;

5、通过屡次消费,链表中只剩下一个节点,此刻value(N-1).value=null,与第二种情况共同;

6、终究一个节点valueN被消费,此刻valueN.value=null, 因为之前valueN是尾节点,所以valueN.next=null。 别的在顾客poll的时分并不会对尾节点做处理,所以此刻尾节点仍是valueN,此刻的情况与1的情况共同。

到这儿最要害的两个办法剖析完结。 下面咱们再回过头来看看前面留下的一个疑问,为啥不支撑这三个办法:remove(Object o),removeAll(Collection),retainAll(Collection)
,现在答复这个问题现已很简略了,假定需求移除中心的一个元素:

1、初始情况为上图中第三行,但节点与节点之间彻底没有相关(第二张图中的左半部分的情况),此刻连遍历一切元素都无法完结,更甭说要去做移除之类的操作了;

2、初始情况为上图中第三行(选这行是因为元素多比较便当看),且此刻前半部分衔接现已树立,后半部分衔接未树立。即default.next = value0, value0.value=value1, 但value1.next = null;

测验移除value1,因为链表可达,此刻咱们顺畅将value1移除,并履行操作value0.next = value1.next,顺畅吧。不过等等,此刻value1.next=null啊,设置完今后value0.next变为null了,整个链表就会分裂成两个链表,后续的poll无法顺畅完结。

3、初始情况为上图中第三行,且链表已构成,要求移除valueN,此刻顾客成功便当到valueN,将value(N-1)设置为尾节点,而一起出产者正要刺进value(N+1)。此刻假如顾客先履行结束出产者后履行,则整个操作能够顺畅完结;假如出产者先履行,则出产者会设置valueN.next = value(N+1),尾节点为value(N+1),顾客后履行,将value(N-1)设置为尾节点,这样value(N+1)就丢掉了。怎样处理?加锁! 好吧,那这个就变成了LinkedBlockingQueue的规划了。

因而,上面这几个办法暂时仍是不能去完成的。

 

版权声明
本文来源于网络,版权归原作者所有,其内容与观点不代表亚美娱乐立场。转载文章仅为传播更有价值的信息,如采编人员采编有误或者版权原因,请与我们联系,我们核实后立即修改或删除。

猜您喜欢的文章

阅读排行

  • 1

    Shell 练习题(append)ITeye

    实例,文件,上述
  • 2

    获取checkbox复选框的值ITeye

    依据,获取,拿到
  • 3

    Redis的耐久化机制ITeye

    耐久,方法,内存
  • 4
  • 5

    记一次线程池的运用ITeye

    线程,运用,行列
  • 6
  • 7
  • 8
  • 9
  • 10

    Digester 解析XMLITeye

    元素,参数,解析