玩转Java线程池(3):如何用松耦合的思想来修改创建线程的策略?

在上一篇:玩转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;
        }
    }
});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章