韩超的博客 (hanchao5272)

路漫漫其修远兮,吾将上下而求索。

Java并发55:并发集合系列-基于预占模式+链表的单向阻塞无界队列LinkedTransferQueue

[超级链接:Java并发学习系列-绪论]
[系列序章:Java并发43:并发集合系列-序章]


原文地址:https://blog.csdn.net/YJian2008/article/details/16951811

TransferQueue

TransferQueue是一个继承了BlockingQueue的接口,并且增加若干新的方法。LinkedTransferQueue是TransferQueue接口的实现类,其定义为一个无界的队列,具有先进先出(FIFO)的特性。

TransferQueue是是ConcurrentLinkedQueue、SynchronousQueue (公平模式下)、无界的LinkedBlockingQueues等的超集。

原理

LinkedTransferQueue采用的一种预占模式。

意思就是消费者线程取元素时,如果队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程park住,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,唤醒该节点上park住线程,被唤醒的消费者线程拿货走人。

这就是预占的意思:有就拿货走人,没有就占个位置等着,等到或超时。

本节内容来源于:https://blog.csdn.net/xiaoxufox/article/details/52241317

方法说明

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():获取所有等待获取元素的消费线程数量。
  6. size():因为队列的异步特性,检测当前队列的元素个数需要逐一迭代,可能会得到一个不太准确的结果,尤其是在遍历时有可能队列发生更改。
  7. 批量操作:类似于addAll,removeAll, retainAll, containsAll, equals, toArray等方法,API不能保证一定会立刻执行。因此,我们在使用过程中,不能有所期待,这是一个具有异步特性的队列。

SynchronousQueue与线程池

其实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算法

transfer算法比较复杂,大致的理解是采用所谓双重数据结构(dual data structures)。

之所以叫双重,其原因是方法都是通过两个步骤完成:保留与完成

  • 比如消费者线程从一个队列中取元素,发现队列为空,他就生成一个空元素放入队列,所谓空元素就是数据项字段为空。然后消费者线程在这个字段上旅转等待。这叫保留。
  • 直到一个生产者线程意欲向队例中放入一个元素,这里他发现最前面的元素的数据项字段为NULL,他就直接把自已数据填充到这个元素中,即完成了元素的传送。

注意事项:

  • 无论是transfer还是tryTransfer方法,在>=1个消费者线程等待获取元素时(此时队列为空),都会立刻转交,这属于线程之间的元素交换。注意,这时,元素并没有进入队列。
  • 在队列中已有数据情况下,transfer将需要等待前面数据被消费掉,直到传递的元素e被消费线程取走为止。
  • 使用transfer方法,工作者线程可能会被阻塞到生产的元素被消费掉为止。
  • 消费者线程等待为零的情况下,各自的处理元素入队与否情况有所不同。
  • size()方法,需要迭代,可能不太准确,尽量不要调用。

实例源码:生产者和消费者进程模拟

生产者源码(Producer):

import java.util.Random;  
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.TransferQueue;  

public class Producer implements Runnable {  
    private final TransferQueue<String> queue;  

    public Producer(TransferQueue<String> queue) {  
        this.queue = queue;  
    }  

    private String produce() {  
        return " your lucky number " + (new Random().nextInt(100));  
    }  

    @Override  
    public void run() {  
        try {  
            while (true) {  
                if (queue.hasWaitingConsumer()) {  
                    queue.transfer(produce());  
                }  
                TimeUnit.SECONDS.sleep(1);//生产者睡眠一秒钟,这样可以看出程序的执行过程  
            }  
        } catch (InterruptedException e) {  
        }  
    }  
}  

消费者源码(Consumer):

import java.util.concurrent.TransferQueue;  

public class Consumer implements Runnable {  
    private final TransferQueue<String> queue;  

    public Consumer(TransferQueue<String> queue) {  
        this.queue = queue;  
    }  

    @Override  
    public void run() {  
        try {  
            System.out.println(" Consumer " + Thread.currentThread().getName()  
                    + queue.take());  
        } catch (InterruptedException e) {  
        }  
    }  
}  

测试类源码:

import java.util.concurrent.LinkedTransferQueue;  
import java.util.concurrent.TransferQueue;  

public class LuckyNumberGenerator {  

    public static void main(String[] args) {  
        TransferQueue<String> queue = new LinkedTransferQueue<String>();  
        Thread producer = new Thread(new Producer(queue));  
        producer.setDaemon(true); //设置为守护进程使得线程执行结束后程序自动结束运行  
        producer.start();  
        for (int i = 0; i < 10; i++) {  
            Thread consumer = new Thread(new Consumer(queue));  
            consumer.setDaemon(true);  
            consumer.start();  
            try {  
                // 消费者进程休眠一秒钟,以便以便生产者获得CPU,从而生产产品  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  

运行结果(一种可能的结果):

Consumer Thread-1 your lucky number 96  
Consumer Thread-2 your lucky number 62  
Consumer Thread-3 your lucky number 49  
Consumer Thread-4 your lucky number 20  
Consumer Thread-5 your lucky number 17  
Consumer Thread-6 your lucky number 52  
Consumer Thread-7 your lucky number 96  
Consumer Thread-8 your lucky number 72  
Consumer Thread-9 your lucky number 45  
Consumer Thread-10 your lucky number 72  
阅读更多
个人分类: Java并发学习总结
所属专栏: Java并发学习实例
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

Java并发55:并发集合系列-基于预占模式+链表的单向阻塞无界队列LinkedTransferQueue

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭