• 3.集--LinkedTransferQueue得知


    近期在阅读开源项目里,发现有几个project都不尽同样地使用LinkedTransferQueue这个数据结构。比方netty,grizzly,xmemcache,Bonecp。

    Bonecp还扩展出一个BoundTransferQueue。
    LinkedTransferQueue最早出如今JSR66R(一个轻量级并行运行框架)包中。眼下已合并到JDK7中。

    JSR66的负责人正是大名顶顶的Doug Lea.
    尽管LinkedTransferQueue被集成在JDK7中,但眼下主流的JDK平台仍然是JDK6。以致开源项目开发人员都不迫及地把他集成在自已的项目中。
    Doug Lea说LinkedTransferQueue是一个聪明的队列。他是ConcurrentLinkedQueue, 
    SynchronousQueue (in “fair” mode), and unbounded LinkedBlockingQueue的超集。



    有一篇论文讨论了其算法与性能:地址:http://www.cs.rice.edu/~wns1/papers/2006-PPoPP-SQ.pdf

    LinkedTransferQueue实现了一个重要的接口TransferQueue,该接口含有以下几个重要方法:
    1. transfer(E e)
       若当前存在一个正在等待获取的消费者线程。即立马移交之;否则,会插入当前元素e到队列尾部,而且等待进入堵塞状态。到有消费者线程取走该元素。
    2. tryTransfer(E e)
       若当前存在一个正在等待获取的消费者线程(使用take()或者poll()函数)。使用该方法会即刻转移/传输对象元素e;
       若不存在,则返回false,而且不进入队列。这是一个不堵塞的操作。


    3. tryTransfer(E e, long timeout, TimeUnit unit)
       若当前存在一个正在等待获取的消费者线程,会马上传输给它; 否则将插入元素e到队列尾部,而且等待被消费者线程获取消费掉,
       若在指定的时间内元素e无法被消费者线程获取。则返回false,同一时候该元素被移除。


    4. hasWaitingConsumer()
       推断是否存在消费者线程
    5. getWaitingConsumerCount()
       获取全部等待获取元素的消费线程数量

    事实上transfer方法在SynchronousQueue的实现中就已存在了,仅仅是没有做为API暴露出来。SynchronousQueue有一个特性:它本身不存在容量,仅仅能进行线程之间的
    元素传送。SynchronousQueue在运行offer操作时。假设没有其它线程运行poll,则直接返回false.线程之间元素传送正是通过transfer方法完毕的。

    有一个使用案例。我们知道ThreadPoolExecutor调节线程的原则是:先调整到最小线程,最小线程用完后,他会将优先将任务放入缓存队列(offer(task)),等缓冲队列用完了,才会向最大线程数调节。这似乎与我们所理解的线程池模型有点不同。我们一般採用添加到最大线程后,才会放入缓冲队列中,以达到最大性能。

    ThreadPoolExecutor代码段:

      public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
            if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
                if (runState == RUNNING && workQueue.offer(command)) {
                    if (runState != RUNNING || poolSize == 0)
                        ensureQueuedTaskHandled(command);
                }
                else if (!addIfUnderMaximumPoolSize(command))
                    reject(command); // is shutdown or saturated
            }
        }
    假设我们採用SynchronousQueue作为ThreadPoolExecuto的缓冲队列时,在没有线程运行poll时(即存在等待线程)。则workQueue.offer(command)返回false,这时ThreadPoolExecutor就会添加线程,最快地达到最大线程数。

    但也仅此而已,也由于SynchronousQueue本身不存在容量,也决定了我们一般无法採用SynchronousQueue作为ThreadPoolExecutor的缓存队列。而一般採用LinkedBlockingQueue的offer方法来实现。

    最新的LinkedTransferQueue或许能够帮我们解决问题,后面再说。



    transfer算法比較复杂,实现非常难看明确。大致的理解是採用所谓双重数据结构(dual data structures)。之所以叫双重,其原因是方法都是通过两个步骤完毕:
    保留与完毕。比方消费者线程从一个队列中取元素,发现队列为空。他就生成一个空元素放入队列,所谓空元素就是数据项字段为空。

    然后消费者线程在这个字段上旅转等待。这叫保留。直到一个生产者线程意欲向队例中放入一个元素,这里他发现最前面的元素的数据项字段为NULL,他就直接把自已数据填充到这个元素中。即完毕了元素的传送。大体是这个意思。这样的方式优美了完毕了线程之间的高效协作。

    对于LinkedTransferQueue,Doug Lea进行了尽乎极致的优化。Grizzly的採用了PaddedAtomicReference:
       public LinkedTransferQueue() {
            QNode dummy = new QNode(null, false);
            head = new PaddedAtomicReference<QNode>(dummy);
            tail = new PaddedAtomicReference<QNode>(dummy);
            cleanMe = new PaddedAtomicReference<QNode>(null);
        }
       static final class PaddedAtomicReference<T> extends AtomicReference<T> {        // enough padding for 64bytes with 4byte refs
            Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;
            PaddedAtomicReference(T r) { super(r); }
        }
    PaddedAtomicReference相对于父类AtomicReference仅仅做了一件事情,就将共享变量追加到64字节。我们能够来计算下。一个对象的引用占4个字节,
    它追加了15个变量共占60个字节,再加上父类的Value变量,一共64个字节。这么做的原因。

    请參考http://www.infoq.com/cn/articles/ftf-java-volatile
    http://rdc.taobao.com/team/jm/archives/1719 这两文章。做JAVA。假设想成为Doug Lea这种大师,也要懂体系结构(待续)


    原文地址:http://guojuanjun.blog.51cto.com/277646/948298/

    版权声明:本文博主原创文章。博客,未经同意不得转载。

  • 相关阅读:
    Discuz!如何设置板块主题分类
    Discuz X2.5 模板目录结构注释说明
    帖子标题颜色-----高亮
    discuz收听
    Discuz伪静态导致 除了首页,其他访问不了
    tag标签划过
    如何取消在线会员右边的下拉菜单,看图
    discuz邮件设置
    2017 ACM/ICPC Asia Regional Qingdao Online
    HDU 5769 Substring 后缀数组
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4872497.html
Copyright © 2020-2023  润新知