您好、欢迎来到现金彩票网!
当前位置:彩之网 > 子池队列 >

面试准备 -- 线程池队列LinkedTransferQueue详解

发布时间:2019-07-25 05:27 来源:未知 编辑:admin

  今天解析的这个队列可能比之前学习的队列都要难,知识点涉及到锁自旋,源代码中出现 CAS 操作也会较难理解。

  看到 BlockingQueue,条件反射下大致知道有哪些方法了。这里接不过多的介绍了,我们来看看 TransferQueue 接口提供了哪些方法:

  表示即时操作,不会对线程造成阻塞。但是有一点需要注意,NOW 常量是 tryTransfer 调用传入的,上面说过 tryTransfer 会可能会出现失败的情况(没有消费线程在等待下会失败),失败的情况下,元素是不会入队的。

  表示异步操作,会往队尾加入元素,插入成功后悔返回 true。上面代码的注释也说明了哪些操作是异步的。注意一点:LinkedTransferQueue 是无界队列,属于不可控。

  表示同步入队,会造成线程阻塞。使用 transfer 方法时,如果没有一个消费线程来消费,那它会一直等待。take 消费线程也同理,只不过 take 操作是队列为空的时候,才会进行阻塞线程。

  这个方法基本上每个入队,出队操作都是调用该方法,haveData 参数传入 true 或者 false,来判别是入队操作还是出队操作。

  接下来,A 线程执行 transfer 方法要入队。线程 A 进来后,发现队列是为空的,且没有阻塞的消费线程。这时,入队操作,走的是分支一,将元素入队后,将 head 指向新入队的节点,最后返回自身节点。操作后如下图:

  这时候线程 B 调用 transfer 方法进行入队。最后,线程会走分支四,将元素入队到队尾,接下来循环走到分支五,将 tail 指向插入的节点。

  注:上述 tail 没有更新,这属于一种 松弛 的方式,我们知道 LinkedTransferQueue 队列是无锁队列,使用 CAS 来进行入队、出队操作,目的是为了节省 CAS 操作的开销。假设保证强的 tail 位置一致,那无锁队列可能还不如直接加个锁更快。这是一个设计上的权衡吧。

  介绍完入队,我们来看看出队的操作。下面用图来解释 LinkedTransferQueue 中 take 方法的出队原理。队列初始状态如下图:

  线程 D 是消费线程,来从队列中取元素。下面代码便是从队列中取出元素的实现:

  认真看一下,将队首节点的 item 设置为 null,并唤醒阻塞的线程 A (线程 A 在队首),然后返回设置在头节点的值对象。

  紧接着,线程 A 被唤醒后,会继续执行。因为唤醒线程 A 前将 item 值置 null 了,所以返回的值为空。之后调用 forgetContents() 方法,将 item 指向自己。下面我们来看看一个方法 awaitMatch:

  从如上代码可以看出,这个一个经典的 “锁优化” 的实现方式,自旋 - yield - 阻塞,我们的线程不会立即进入阻塞状态,而是要经过自旋一定次数后依旧没被消费才会进行线程阻塞。

  根据上图,我们可以看到, 原来的队首节点将的 next 自己指向自己,head 指向第三个节点,一次性跳了两个节点。 这就是 “松弛” 策略。上图线程被唤醒后进行的一系列操作和原来相同,不继续介绍,直接到最后结构:

  可以看到 tail 还是指向那个自身的节点,没有切换。下面在来一条线程 G:

  线程 G 也是消费线程,入队后发现队列空了,只好线程阻塞,等下入队。不过这里有一点注意:因为是有阻塞的消费线程,这时如果入队线程一来,会直接将元素交给等待的消费线程,而不是入队后,在唤醒消费线程取消费。

  LinkedTransferQueue 因为是 1.7 才出的,集合了前面几个队列的优秀的地方,设计上感觉很别扭。并且极难理解。

http://ibtlsports.com/zichiduilie/261.html
锟斤拷锟斤拷锟斤拷QQ微锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷
关于我们|联系我们|版权声明|网站地图|
Copyright © 2002-2019 现金彩票 版权所有