在上一篇:玩转Java线程池(2):Tomcat是如何修改创建线程的策略的?中,我介绍了 Tomcat 是如何去改变原来的JDK中的创建线程的过程的。从中,我们发现 Tomcat
为了达到改变创建过程的目的,继承原来的ThreadPoolExecutor
,重写了 execute
方法,而且为了配合重写后的 execute
的方法的使用,还实现了一个新的阻塞队列 TaskQueue
,二者配合使用,可以算是一种紧耦合的实现了。那么有没有一种比较简单的实现?因为在这个方案里,我们需要一次性实现两个新的类,还需要把二者配合使用。Tomcat
的实现略显复杂,我们的使用往往追求简单、高效,所以需要一种更加简单的实线方式。
注意,这里的实现的思路都来自这个回答:How to get the ThreadPoolExecutor to increase threads to max before queueing?。在这个回答的下面有很多不错的思路。
1 阻塞队列 和 拒绝策略 的 松耦合的实现
这里的实现主要是来自这个回答
https://stackoverflow.com/questions/19528304/how-to-get-the-threadpoolexecutor-to-increase-threads-to-max-before-queueing/19528305#19528305
其实具体的思想还是和 Tomcat
的实现思想差不多,只不过做了删减。
1.1 阻塞队列的实现
阻塞队列仅仅是重载了 offer
方法。
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
@Override
public boolean offer(Runnable e) {
if (size() == 0) {
return super.offer(e);
} else {
return false;
}
}
};
如果这个是空的队列,那么就把任务进队,如果此时的队列是非空的,那么就返回 false
。
为什么要保证队列非空才把任务进队?我推测是为了保证这个时候的线程都是处于满载的状态
1 如果这个时候队列里面有一个元素,那么,整个线程池就是没有空闲的线程的。那么创建新的工作线程就成了理所应当的事情了。
2 如果这个时候队列里面没有元素的话,那么很大概率此时的线程池是有空闲线程的,那么你入队,这个任务会马上被工作线程消费。
1.2 拒绝策略
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
executor.getQueue().put(r);
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + executor);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
这个实现就简单了,就是利用了LinkedBlockingQueue
de put()
的方法,这个方法的作用就是,在进行入队操作的时候,如果队列是满的状态,那么就会继续阻塞,直到队列非空的时候,再进行如对操作。但是这个存在缺陷,就是这个 put()
没有设置等待超时机制。这样如果线程池的某些任务出现了差错,就会导致线程池的提交任务出现阻塞。
所以如果不是那种重要的任务,允许丢失,或者,在抛出异常的时候有保存任务的机制,那么就可以使用有超时机制的操作,比如用 boolean offer(E e, long timeout, TimeUnit unit)
这样就能保证在一定时间之后,放弃入队操作了。比如 在Tomcat 的实现的 阻塞队列(TaskQueue) 实现中用的就是这个操作
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if ( parent==null || parent.isShutdown() )
throw new RejectedExecutionException(sm.getString("taskQueue.notRunning"));
return super.offer(o,timeout,unit); // 进行如对操作。
}
1.3 完整代码
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
@Override
public boolean offer(Runnable e) {
if (size() == 0) {
return super.offer(e);
} else {
return false;
}
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/,
50 /*max*/,
60 /*secs*/,
TimeUnit.SECONDS,
queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
executor.getQueue().put(r);
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + executor);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
});