public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
workQueue
:它决定了缓存任务的排队策略。对于不同的应用场景我们可能会采取不同的排队策略,这就需要不同类型的队列。这个队列需要一个实现了BlockingQueue接口的任务等待队列。
在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue
、LinkedBlockingQueue
和 ArrayBlockingQueue
● 队列:是一种特殊的线性结构,允许在线性结构的前端进行删除/读取操作;允许在线性结构的后端进行插入操作;这种线性结构具有“先进先出”的操作特点:
但是在实际应用中,队列中的元素有可能不是以“进入的顺序”为排序依据的。例如我们将要讲到的 PriorityBlockingQueue
队列。
● 栈:栈也是一种线性结构,但是栈和队列相比只允许在线性结构的一端进行操作,入栈和出栈都是在一端完成。
“是这样 一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量。翻译一下:这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。
SynchronousQueue<Object> queue = new SynchronousQueue<Object>();
// 不要使用add,因为这个队列内部没有任何容量,所以会抛出异常“IllegalStateException”
// queue.add(new Object());
// 操作线程会在这里被阻塞,直到有其他操作线程取走这个对象
queue.put(new Object());
一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
// 我们创建了一个ArrayBlockingQueue,并且设置队列空间为2
ArrayBlockingQueue
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());
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());
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;
}
}
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();
}