线程池更激进一点,优先开启更多的线程

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值