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