1. 程式人生 > >java執行緒池介紹及簡單使用舉例

java執行緒池介紹及簡單使用舉例

多執行緒雖然能夠提升程式的效能,但其實也是一把雙刃劍。"為每一個任務分配一個執行緒"的問題在於資源管理的複雜性。當我們需要頻繁的建立多個執行緒進行耗時操作時,每次通過new Thread來建立並不是一種好的辦法。new Thread 新建和銷燬物件的效能較差,執行緒缺乏統一的管理,而且可能出現無限制的建立執行緒。

每當看到這種形式的程式碼時:new Thread(runnable).start()

      並且你希望獲得一種更靈活的執行策略,請考慮使用Executor來替代Thread。(參考《Java併發程式設計實戰》)

      因此,我們可以通過使用執行緒池來管理執行緒。執行緒池是指管理一組同構工作執行緒的資源池。其原理簡單解釋就是會建立多個執行緒並進行管理,提交給執行緒的任務會被執行緒池指派給其中的執行緒進行執行。

      java.util.concurrent提供了一種靈活的執行緒池實現作為Executor框架的一部分,Executor介面如下:

    public interface Executor {
         void execute(Runnable command);
    }
     雖然Executor是個簡單的介面,但它缺位靈活且強大的非同步任務執行框架提供了基礎。

     執行緒池都實現了ExecutorService介面,該介面定義了執行緒池需要實現的方法:

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, Ti
      meUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    //.......(程式碼省略其他用於任務提交的便利方法)
}
   ExecutorService介面的實現有,ThreadPoolExecutor和ScheduledThreadPoolExcecutor。

   ThreadPoolExecutor繼承自抽象類AbstractExecutorService,該抽象類實現了ExecutorService介面。ThreadPoolExecutor也是我們運用最多的執行緒池。

    ScheduledThreadPoolExcecutor 繼承了ThreadPoolExecutor 並實現了ScheduledExecutorService介面,ScheduledExecutorService介面繼承自ExecutorService介面。ScheduledThreadPoolExcecutor

用於週期性的執行任務,和Timer類類似。但Timer存在一些缺陷,因此應該考慮使用ScheduledThreadPoolExcecutor來代替它(Timer支援基於絕對時間而不是相對時間的排程機制,因此任務的執行對系統時鐘變化很敏感,而ScheduledThreadPoolExcecutor只支援基於相對時間的排程)。

    我們可以通過ThreadPoolExecutor的建構函式來例項化一個物件,但由於建立引數相對複雜,通常選擇Exectors工廠類的靜態方法來建立一個執行緒池:

    newFixedThreadPool。將建立一個固定長度的執行緒池,每當提交一個任務時就建立一個執行緒池,直到達到執行緒池的最大數量,這時執行緒池的規模將不再變化。

    newCachedThreadPool。將建立一個可快取的執行緒池,如果執行緒池的當前規模超過了處理需求時,那麼將回收空閒的執行緒,當需求增加時,則可以新增新的執行緒,執行緒池的規模不存在任何限制。

    newSingleThreadPool。是一個單執行緒的Executor,它建立單個工作執行緒來執行任務,如果這個執行緒異常結束,會建立另一個執行緒來替代。newSingleThreadPool能確保依照任務在佇列中的順序來序列執行(例如FIFO、LIFO、優先順序)。

    newScheduledThreadPool。建立了一個固定長度的執行緒池,而且以延遲或定時的方式來執行任務,類似於Timer。

    newFixedThreadPool和newCachedThreadPool 返回通用的ThreadPoolExecutor例項,這些例項可以直接用來構造專門用途的executor。

   當預設的建立執行緒池策略無法滿足要求時,那麼可以通過ThreadPoolExecutor建構函式來例項化一個物件,根據自己的需求來實現定製,ThreadPoolExecutor最常見的建構函式形式如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
引數說明:

corePoolSize:執行緒池中所儲存的核心執行緒數。執行緒池啟動後預設是空的,只有任務來臨時才會建立執行緒以處理請求。

maximumPoolSize:執行緒池允許建立的最大執行緒數。它與corePoolSize的作用是調整“執行緒池中實際執行的執行緒的數量”。當新任務提交給執行緒池時,如果執行緒池中執行的執行緒數量小於corePoolSize,則建立新執行緒來執行任務;如果此時,執行緒池中執行的執行緒數量大於corePoolSize,但小於maximumPoolSize,則僅當阻塞佇列滿時才建立新執行緒。如果corePoolSize與maximumPoolSize相同,則建立固定大小的執行緒池。如果將maximumPoolSize設定為基本的無界值(如Integer.MAX_VALUE),則允許執行緒池適應任意數量的併發任務。

keepAliveTime:當前執行緒池執行緒總數達到核心執行緒數時,終止多餘的空閒執行緒的時間。

Unit:keepAliveTime引數的時間單位,可選值有毫秒、秒、分等。

workQueue:任務佇列。如果當前執行緒池達到核心執行緒數量corePoolSize後,且當前所有執行緒都處於活動狀態時,則將新加入的任務放到此佇列中。

threadFactory:執行緒工廠,讓使用者可以定製執行緒的建立過程,一般不需要設定。

Handler:拒絕策略,當執行緒池和任務佇列workQueue都滿了的情況下,對新加的任務採取的處理策略。

下面通過兩個簡單的例子來說明執行緒池的簡單使用:

1.通過Executors.newFixedThreadPool(int)來建立一個固定數量的執行緒池,程式碼如下:

public class MyExecutorDemo {
    //執行的任務數量
    private static int MAX = 10;

    public static void main(String args[]){
        try {
            fixedThreadPool(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    private static void fixedThreadPool (int coreSize)
            throws InterruptedException,ExecutionException {
        //建立執行緒池
        ExecutorService exec = Executors.newFixedThreadPool(coreSize);
        for(int i = 0; i < MAX; i++){
            //提交任務
            Future<Integer> task = exec.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    System.out.println("執行執行緒" + Thread.currentThread().getName());
                    return fibc(20);
                }
            });
            //獲取執行結果
            System.out.println("第"+i+"次計算,結果為"+task.get());
        }
    }
    //模擬耗時操作,定義一個斐波那契數列
    private static int fibc(int num){
        if (num == 0){
            return 0;
        }
        if (num == 1){
            return  1;
        }
        return fibc(num-1)+fibc(num-2);
    }

}
在上述程式碼中,通過fixedThreadPool啟動了含有4個執行緒的執行緒池,我們看一下該方法的建構函式,
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
第一個和第二個引數時相同的值,則建立固定大小的執行緒池,最後的引數為無界佇列,因此該執行緒池可容納無限個任務。建立成功後,向執行緒池中通過submit提交了10個Callable任務,每個任務計算前20個的斐波那契數列。通過結果可以看出,執行緒池中有4個執行緒交替的執行任務,執行運算結果如下:

執行執行緒pool-1-thread-1
第0次計算,結果為6765
執行執行緒pool-1-thread-2
第1次計算,結果為6765
執行執行緒pool-1-thread-3
第2次計算,結果為6765
執行執行緒pool-1-thread-4
第3次計算,結果為6765
執行執行緒pool-1-thread-1
第4次計算,結果為6765
執行執行緒pool-1-thread-2
第5次計算,結果為6765
執行執行緒pool-1-thread-3
第6次計算,結果為6765
執行執行緒pool-1-thread-4
第7次計算,結果為6765
執行執行緒pool-1-thread-1
第8次計算,結果為6765
執行執行緒pool-1-thread-2
第9次計算,結果為6765

newCachedThreadPool

2.通過Executors.newCacheThreadPool()建立帶快取的執行緒池

    有時,我們需要任務儘可能快的被執行,這需要執行緒池中的執行緒足夠多,也就是說需要用空間來換時間,建立的執行緒越多,佔用的記憶體消耗也越大。但由於其併發量也越大,因此執行的速度也越快。考慮這樣一種場景,在新提交了一個任務後,由於當前沒有空閒執行緒可執行,因此需要馬上建立一個執行緒來執行該任務。這種場景可通過該方法來實現。程式碼如下:

private static void newCachedThreadPool () throws ExecutionException,
            InterruptedException{

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < MAX; i++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執行執行緒為:"+Thread.currentThread().getName()+
                            ",結果為:"+fibc(30));
                }
            });
        }
    }

程式碼中,通過newFixedThreadPool建立了一個執行緒池,並每次提交一個任務,來計算前30個的斐波那契數列,執行結果如下:

執行執行緒為:pool-1-thread-2,結果為:832040
執行執行緒為:pool-1-thread-1,結果為:832040
執行執行緒為:pool-1-thread-3,結果為:832040
執行執行緒為:pool-1-thread-4,結果為:832040
執行執行緒為:pool-1-thread-1,結果為:832040
執行執行緒為:pool-1-thread-5,結果為:832040
執行執行緒為:pool-1-thread-2,結果為:832040
執行執行緒為:pool-1-thread-6,結果為:832040
執行執行緒為:pool-1-thread-1,結果為:832040
執行執行緒為:pool-1-thread-5,結果為:832040

為了保證執行效率,每次提交任務,執行緒池都會建立一個新執行緒來執行任務,但前提是此時沒有空閒執行緒才建立新執行緒,但有空閒執行緒時,則會使用空閒的執行緒來執行任務,如結果中所示,執行前4個任務時,執行緒池為每個任務都建立了一個執行緒,當執行到第5個任務時,此時第一個執行緒已經執行完任務,並處於空閒狀態,那麼第5個任務就被執行在第1個執行緒中了。

(參考書籍《Java併發程式設計實戰》、《Android開發進階從小工到專家》)