ThreadPoolExecutor的 workQueue 任务队列详解

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

workQueue :它决定了缓存任务的排队策略。对于不同的应用场景我们可能会采取不同的排队策略,这就需要不同类型的队列。这个队列需要一个实现了BlockingQueue接口的任务等待队列。

在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueueLinkedBlockingQueueArrayBlockingQueue

1、队列和栈

● 队列:是一种特殊的线性结构,允许在线性结构的前端进行删除/读取操作;允许在线性结构的后端进行插入操作;这种线性结构具有“先进先出”的操作特点:
在这里插入图片描述

但是在实际应用中,队列中的元素有可能不是以“进入的顺序”为排序依据的。例如我们将要讲到的 PriorityBlockingQueue 队列。

● 栈:栈也是一种线性结构,但是栈和队列相比只允许在线性结构的一端进行操作,入栈和出栈都是在一端完成。

在这里插入图片描述

2、有限队列

2.1、SynchronousQueue:

“是这样 一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量。翻译一下:这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。

SynchronousQueue<Object> queue = new SynchronousQueue<Object>();
// 不要使用add,因为这个队列内部没有任何容量,所以会抛出异常“IllegalStateException”
// queue.add(new Object());
// 操作线程会在这里被阻塞,直到有其他操作线程取走这个对象
queue.put(new Object());

2.2、 ArrayBlockingQueue:

一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。

// 我们创建了一个ArrayBlockingQueue,并且设置队列空间为2
ArrayBlockingQueue<Object> arrayQueue = new ArrayBlockingQueue<Object>(2);
// 插入第一个对象
arrayQueue.put(new Object());
// 插入第二个对象
arrayQueue.put(new Object());
// 插入第三个对象时,这个操作线程就会被阻塞。
arrayQueue.put(new Object());
// 请不要使用add操作,和SynchronousQueue的add操作一样,它们都使用了AbstractQueue中的add实现

3、 无限队列

3.1、 LinkedBlockingQueue:

LinkedBlockingQueue 是我们在 ThreadPoolExecutor线程池中常用的等待队列。它可以指定容量也可以不指定容量。由于它具有“无限容量”的特性,所以我还是将它归入了无限队列的范畴(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)。
LinkedBlockingQueue 的实现是基于链表结构,而不是类似 ArrayBlockingQueue 那样的数组。但实际使用过程中,不需要关心它的内部实现,如果指定了LinkedBlockingQueue 的容量大小,那么它反映出来的使用特性就和 ArrayBlockingQueue 类似了。

LinkedBlockingQueue<Object> linkedQueue = new LinkedBlockingQueue<Object>(2);
linkedQueue.put(new Object());
// 插入第二个对象
linkedQueue.put(new Object());
// 插入第三个对象时,这个操作线程就会被阻塞。
linkedQueue.put(new Object());

//=======================================

// 或者如下使用:

LinkedBlockingQueue<Object> linkedQueue = new LinkedBlockingQueue<Object>();
linkedQueue.put(new Object());
// 插入第二个对象
linkedQueue.put(new Object());
// 插入第N个对象时,都不会阻塞
linkedQueue.put(new Object());

3.2、 LinkedBlockingDeque

LinkedBlockingDeque 是一个基于链表的双端队列。LinkedBlockingQueue 的内部结构决定了它只能从队列尾部插入,从队列头部取出元素;但是 LinkedBlockingDeque 既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。

LinkedBlockingDeque<TempObject> linkedDeque = new LinkedBlockingDeque<TempObject>();
// push ,可以从队列的头部插入元素
linkedDeque.push(new TempObject(1));
linkedDeque.push(new TempObject(2));
linkedDeque.push(new TempObject(3));
// poll , 可以从队列的头部取出元素
TempObject tempObject = linkedDeque.poll();
// 这里会打印 tempObject.index = 3
System.out.println("tempObject.index = " + tempObject.getIndex());

// put , 可以从队列的尾部插入元素
linkedDeque.put(new TempObject(4));
linkedDeque.put(new TempObject(5));
// pollLast , 可以从队列尾部取出元素
tempObject = linkedDeque.pollLast();
// 这里会打印 tempObject.index = 5
System.out.println("tempObject.index = " + tempObject.getIndex());

3.3、PriorityBlockingQueue

PriorityBlockingQueue 是一个按照优先级进行内部元素排序的无限队列。存放在PriorityBlockingQueue 中的元素必须实现 Comparable 接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue 不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。

这是什么意思呢? PriorityBlockingQueue并不保证除了队列头部以外的元素排序一定是正确的。请看下面的示例代码:

PriorityBlockingQueue<TempObject> priorityQueue = new PriorityBlockingQueue<TempObject>();
priorityQueue.put(new TempObject(-5));
priorityQueue.put(new TempObject(5));
priorityQueue.put(new TempObject(-1));
priorityQueue.put(new TempObject(1));

// 第一个元素是5
// 实际上在还没有执行priorityQueue.poll()语句的时候,队列中的第二个元素不一定是1
TempObject targetTempObject = priorityQueue.poll();
System.out.println("tempObject.index = " + targetTempObject.getIndex());
// 第二个元素是1
targetTempObject = priorityQueue.poll();
System.out.println("tempObject.index = " + targetTempObject.getIndex());
// 第三个元素是-1
targetTempObject = priorityQueue.poll();
System.out.println("tempObject.index = " + targetTempObject.getIndex());
// 第四个元素是-5
targetTempObject = priorityQueue.poll();
System.out.println("tempObject.index = " + targetTempObject.getIndex());
// 这个元素类,必须实现Comparable接口
private static class TempObject implements Comparable<TempObject> {
    private int index;

    public TempObject(int index) {
        this.index = index;
    }

    /**
     * @return the index
     */
    public int getIndex() {
        return index;
    }

    /* (non-Javadoc)
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo(TempObject o) {
        return o.getIndex() - this.index;
    }

}

3.4、LinkedTransferQueue

LinkedTransferQueue 也是一个无限队列,它除了具有一般队列的操作特性外(先进先出),还具有一个阻塞特性:LinkedTransferQueue可以由一对生产者/消费者线程进行操作,当消费者将一个新的元素插入队列后,消费者线程将会一直等待,直到某一个消费者线程将这个元素取走,反之亦然。

LinkedTransferQueue 的操作特性可以由下面这段代码提现。在下面的代码片段中,有两中类型的线程:生产者和消费者,这两类线程互相等待对方的操作:

/**
 * 生产者线程
 */
private static class ProducerRunnable implements Runnable {

    private LinkedTransferQueue<TempObject> linkedQueue;

    public ProducerRunnable(LinkedTransferQueue<TempObject> linkedQueue) {
        this.linkedQueue = linkedQueue;
    }

    @Override
    public void run() {
        for(int index = 1 ; ; index++) {
            try {
                // 向LinkedTransferQueue队列插入一个新的元素
                // 然后生产者线程就会等待,直到有一个消费者将这个元素从队列中取走
                this.linkedQueue.transfer(new TempObject(index));
            } catch (InterruptedException e) {
                e.printStackTrace(System.out);
            }
        }
    }
}

/**
 * 消费者线程
 */
private static class ConsumerRunnable implements Runnable {

    private LinkedTransferQueue<TempObject> linkedQueue;

    public ConsumerRunnable(LinkedTransferQueue<TempObject> linkedQueue) {
        this.linkedQueue = linkedQueue;
    }

    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();

        while(!currentThread.isInterrupted()) {
            try {
                // 等待,直到从LinkedTransferQueue队列中得到一个元素
                TempObject targetObject = this.linkedQueue.take();
                System.out.println("线程(" + currentThread.getId() + ")取得targetObject.index = " + targetObject.getIndex());
            } catch (InterruptedException e) {
                e.printStackTrace(System.out);
            }
        }
    }
}

以下是启动代码:

LinkedTransferQueue<TempObject> linkedQueue = new LinkedTransferQueue<TempObject>();
// 这是一个生产者线程
Thread producerThread = new Thread(new ProducerRunnable(linkedQueue));
// 这里有两个消费者线程
Thread consumerRunnable1 = new Thread(new ConsumerRunnable(linkedQueue));
Thread consumerRunnable2 = new Thread(new ConsumerRunnable(linkedQueue));

// 开始运行
producerThread.start();
consumerRunnable1.start();
consumerRunnable2.start();

// 这里只是为了main不退出,没有任何演示含义
Thread currentThread = Thread.currentThread();
synchronized (currentThread) {
    currentThread.wait();
}

猜你喜欢

转载自blog.csdn.net/xiaojin21cen/article/details/87363143