Java 线程池是先用工作队列来存放来不及处理的任务,满了之后再扩容线程池。当我们的工作队列设置得很大时,最大线程数这个参数显得没有意义,因为队列很难满,或者到满的时候再去扩容线程池已经于事无补了。
我们有没有办法让线程池更激进一点,优先开启更多的线程,而把队列当成一个后备方案呢?
大致思路:
1.由于线程池在工作队列满了无法入队的情况下会扩容线程池,那么我们是否可以重写队列的 offer 方法,造成这个队列已满的假象呢?
2.由于我们 Hack 了队列,在达到了最大线程后势必会触发拒绝策略,那么能否实现一个自定义的拒绝策略处理程序,这个时候再把任务真正插入队列呢?
方案1
// extend LinkedBlockingQueue to force offer() to return false conditionally
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
private static final long serialVersionUID = -6903933921423432194L;
@Override
public boolean offer(Runnable e) {
// Offer it to the queue if there is 0 items already queued, else
// return false so the TPE will add another thread. If we return false
// and max threads have been reached then the RejectedExecutionHandler
// will be called which will do the put into the queue.
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 {
// This does the actual put into the queue. Once the max threads
// have been reached, the tasks will then queue up.
executor.getQueue().put(r);
// we do this after the put() to stop race conditions
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + e);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
});
当向队列提交任务时,ThreadPoolExecutor将:
1.最初将线程数量扩展到核心大小
2.把任务offer进队列,(如果队列为空,它将被空闲线程处理)
3.如果队列已经有1个或多个元素,则offer(…)将返回false。
4.如果返回false,则增加池中的线程数,直到达到最大值
5.如果达到最大值,那么它调用RejectedExecutionHandler
6.RejectedExecutionHandler然后将任务放入队列中,由第一个可用线程以FIFO顺序处理。
方案评价:任务被执行的顺序问题,当任务1、任务2被放入队列,任务3在执行,假如任务3特别耗时,任务1和2就会不能即使被执行
方案2
重写 ourQueue(抽象的) ,并且将线程池设置到队列的属性中,ourQueue.setThreadPoolExecutor(tpe),这种方案会耦合队列和线程池
此时我们的offer方法就变成
int poolSize = tpe.getPoolSize();
int maximumPoolSize = tpe.getMaximumPoolSize();
if (poolSize >= maximumPoolSize || poolSize > tpe.getActiveCount()) {
return super.offer(e);
} else {
return false;
}
方案评价:因为通过访问 volatile属性(getActiveCount()、getPoolSize()) 或者 锁住 TPE 并遍历线程列表。此外,这里还存在并发条件,这些条件可能导致任务不正确地入队(也就是有序性,上一个任务进了队列,下一个任务却直接fork一个线程执行),或者在存在空闲线程时导致另一个线程分叉。
方案3:
解决方案是使用Java 7 LinkedTransferQueue并让offer()调用tryTransfer()。当有一个等待的消费者线程时,任务将被传递给该线程。否则,offer()将返回false, ThreadPoolExecutor将生成一个新线程。
BlockingQueue<Runnable> queue = new LinkedTransferQueue<Runnable>() {
@Override
public boolean offer(Runnable e) {
return tryTransfer(e);
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 50, 60, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
executor.getQueue().put(r);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
方案的唯一缺点是LinkedTransferQueue是无界的。
tomcat也实现了这个idea
参考:
https://github.com/apache/tomcat/blob/a801409b37294c3f3dd5590453fb9580d7e33af2/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java
https://stackoverflow.com/questions/19528304/how-to-get-the-threadpoolexecutor-to-increase-threads-to-max-before-queueing