Java 線程池的使用詳解

線程池的使用詳解

採用線程池的好處

  在這裏我們首先來說一下采用線程池的好處。 
  1. 重用線程池中已經存在的線程,減少了線程的創建和消亡多造成的性能開銷。 
  2. 能夠有效控制最大的併發線程數,提高了系統資源的使用率,並且還能夠避免大量線程之間因爲相互搶佔系統資源而導致阻塞。 
  3. 能夠對線程進行簡單管理,並提供定時執行、定期執行、單線程、併發數控制等功能。

ThreadPoolExecutor

  我們可以通過ThreadPoolExecutor來創建一個線程池。下面我們就來看一下ThreadPoolExecutor中的一個構造方法。

public <strong>ThreadPoolExecutor</strong>(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

ThreadPoolExecutor參數含義

1. int corePoolSize 
  線程池中的核心線程數,默認情況下,核心線程一直存活在線程池中,即便他們在線程池中處於閒置狀態。除非我們將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設爲true的時候,這時候處於閒置的核心線程在等待新任務到來時會有超時策略,這個超時時間由keepAliveTime來指定。一旦超過所設置的超時時間,閒置的核心線程就會被終止。 
2. int maximumPoolSize 
  線程池中所容納的最大線程數,如果活動的線程達到這個數值以後,後續的新任務將會被阻塞。 
3. long keepAliveTime 
  非核心線程閒置時的超時時長,對於非核心線程,閒置時間超過這個時間,非核心線程就會被回收。只有對ThreadPoolExecutor的allowCoreThreadTimeOut屬性設爲true的時候,這個超時時間纔會對核心線程產生效果。 
4. TimeUnit unit 
  用於指定keepAliveTime參數的時間單位。他是一個枚舉,可以使用的單位有天(TimeUnit.DAYS),小時(TimeUnit.HOURS),分鐘(TimeUnit.MINUTES),毫秒(TimeUnit.MILLISECONDS),微秒(TimeUnit.MICROSECONDS, 千分之一毫秒)和毫微秒(TimeUnit.NANOSECONDS, 千分之一微秒); 
5. BlockingQueue workQueue
  線程池中保存等待執行的任務的阻塞隊列。通過線程池中的execute方法提交的Runable對象都會存儲在該隊列中。我們可以選擇下面幾個阻塞隊列。

  • ArrayBlockingQueue:基於數組實現的有界的阻塞隊列,該隊列按照FIFO(先進先出)原則對隊列中的元素進行排序。
  • LinkedBlockingQueue:基於鏈表實現的阻塞隊列,該隊列按照FIFO(先進先出)原則對隊列中的元素進行排序。
  • SynchronousQueue:內部沒有任何容量的阻塞隊列。在它內部沒有任何的緩存空間。對於SynchronousQueue中的數據元素只有當我們試着取走的時候纔可能存在。
  • PriorityBlockingQueue:具有優先級的無限阻塞隊列。
  • 我們還能夠通過實現BlockingQueue接口來自定義我們所需要的阻塞隊列。

6. ThreadFactory threadFactory 
  線程工廠,爲線程池提供新線程的創建。ThreadFactory是一個接口,裏面只有一個newThread方法。 
7. RejectedExecutionHandler handler 
  他是RejectedExecutionHandler對象,而RejectedExecutionHandler是一個接口,裏面只有一個rejectedExecution方法。當任務隊列已滿並且線程池中的活動線程已經達到所限定的最大值或者是無法成功執行任務,這時候ThreadPoolExecutor會調用RejectedExecutionHandler中的rejectedExecution方法。在ThreadPoolExecutor中有四個內部類實現了RejectedExecutionHandler接口。在線程池中它默認是AbortPolicy,在無法處理新任務時拋出RejectedExecutionException異常。下面是在ThreadPoolExecutor中提供的四個可選值。

  • CallerRunsPolicy:只用調用者所在線程來運行任務。
  • AbortPolicy:直接拋出RejectedExecutionException異常。
  • DiscardPolicy:丟棄掉該任務,不進行處理
  • DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
  • 我們也可以通過實現RejectedExecutionHandler接口來自定義我們自己的handler。如記錄日誌或持久化不能處理的任務。

ThreadPoolExecutor執行規則

  1. 如果在線程池中的線程數量沒有達到核心的線程數量,這時候就回啓動一個核心線程來執行任務。
  2. 如果線程池中的線程數量已經超過核心線程數,這時候任務就會被插入到任務隊列中排隊等待執行。
  3. 由於任務隊列已滿,無法將任務插入到任務隊列中。這個時候如果線程池中的線程數量沒有達到線程池所設定的最大值,那麼這時候就會立即啓動一個非核心線程來執行任務。
  4. 如果線程池中的數量達到了所規定的最大值,那麼就會拒絕執行此任務,這時候就會調用RejectedExecutionHandler中的rejectedExecution方法來通知調用者。

ThreadPoolExecutor的使用

  上面說了那麼多,我們現在就來看一下到底是如何使用這個ThreadPoolExecutor。首先我們通過ThreadPoolExecutor創建一個一個線程池。

ExecutorService executorService = new ThreadPoolExecutor(5,10,10,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>());

  對於ThreadPoolExecutor有多個構造方法,對於上面的構造方法中的其他參數都採用默認值。我們創建完一個線程池以後,下面就再來看一下如何向線程池提交一個任務。我們可以通過execute和submit兩種方式來向線程池提交一個任務。 
execute 
  當我們使用execute來提交任務時,由於execute方法沒有返回值,所以說我們也就無法判定任務是否被線程池執行成功。

executorService.execute(new Runnable() {

    @Override
    public void run() {
        // todo something
    }
});

submit 
  當我們使用submit來提交任務時,它會返回一個future,我們就可以通過這個future來判斷任務是否執行成功,通過future的get方法來獲取返回值,get方法會阻塞住直到任務完成,而使用get(long timeout, TimeUnit unit)方法則會阻塞一段時間後立即返回,這時候有可能任務並沒有執行完。

Future<Object> future = executorService.submit(new Callable<Object>() {
    @Override
    public String call() throws Exception {
        // TODO Auto-generated method stub
        return null;
    }
});

try {
    Object object = future.get();
} catch (InterruptedException e) {
    // 處理中斷異常
    e.printStackTrace();
} catch (ExecutionException e) {
    // 處理無法執行異常
    e.printStackTrace();
}

關閉線程池

  我們可以通過shutdown方法或者shutdownNow方法來關閉線程池。對於這兩種關閉線程池的方式他們都是通過遍歷線程池中所有的線程,然後依次調用線程的interrupt方法來中斷線程。當然對於這兩種關閉線程池的方法也是有一定區別的(具體區別見下面註釋)。 
  當我們調用了下面任何一個關閉方法時,isShutdown方法就會返回true。而當線程池關閉成功以後isTerminaed方法會返回true。對於線程池中的正在執行的任務如果我們希望他們執行完成以後再去關閉線程池則調用shutdown方法;而我們希望在關閉線程池的時候中斷線程池內正在執行的任務,則調用shutdownNow方法。

/**
* 首先將線程池的狀態設置成SHUTDOWN狀態,然後中斷所
* 有沒有正在執行任務的線程。
*/
executorService.shutdown();

/**
* 首先將線程池的狀態設置爲STOP,然後開始嘗試停止所有的正在
* 工作或暫停任務的線程
*/
executorService.shutdownNow();

Java線程池

Java中的線程池分類

  在這裏我們介紹一下Java中四種具有不同功能常見的線程池。他們都是直接或者間接配置ThreadPoolExecutor來實現他們各自的功能。這四種線程池分別是newFixedThreadPool,newCachedThreadPool,newScheduledThreadPool和newSingleThreadExecutor。這四個線程池可以通過Executors類獲取。下面分別介紹這四種線程池。 
   1. newFixedThreadPool 
  我們可以通過Executors中的newFixedThreadPool方法來創建,該線程池是一種線程數量固定的線程池。在這個線程池中所容納最大的線程數就是我們設置的核心線程數。如果線程池的線程處於空閒狀態的話,它們並不會被回收,除非是這個線程池被關閉。如果所有的線程都處於活動狀態的話,新任務就回處於等待狀態,直到有線程空閒出來。由於newFixedThreadPool只有核心線程,並且這些線程都不會被回收,也就是它能夠更快速的響應外界請求。從下面的newFixedThreadPool方法的實現可以看出,newFixedThreadPool只有核心線程,並且不存在超時機制,採用LinkedBlockingQueue,所以對於任務隊列的大小也是沒有限制的。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

   2. newCachedThreadPool 
  我們可以通過Executors中的newCachedThreadPool方法來創建,通過下面的newCachedThreadPoolfan’f在這裏我們可以看出它的核心線程數爲0,線程池的最大線程數Integer.MAX_VALUE。而Integer.MAX_VALUE是一個很大的數,也差不多可以說這個線程池中的最大線程數可以任意大。當線程池中的線程都處於活動狀態的時候,線程池就會創建一個新的線程來處理任務。該線程池中的線程超時時長爲60秒,所以當線程處於閒置狀態超過60秒的時候便會被回收。這也就意味着若是整個線程池的線程都處於閒置狀態超過60秒以後,在newCachedThreadPool線程池中是不存在任何線程的,所以這時候它幾乎不佔用任何的系統資源。對於newCachedThreadPool他的任務隊列採用的是SynchronousQueue,上面說到在SynchronousQueue內部沒有任何容量的阻塞隊列。SynchronousQueue內部相當於一個空集合,我們無法將一個任務插入到SynchronousQueue中。所以說在線程池中如果現有線程無法接收任務,將會創建新的線程來執行任務。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

   3. newScheduledThreadPool 
  我們可以通過Executors中的newScheduledThreadPool方法來創建,它的核心線程數是固定的,對於非核心線程幾乎可以說是沒有限制的,並且當非核心線程處於限制狀態的時候就會立即被回收。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

   4. newSingleThreadExecutor 
  我們可以通過Executors中的newSingleThreadExecutor方法來創建,在這個線程池中只有一個核心線程,對於任務隊列沒有大小限制,也就意味着這一個任務處於活動狀態時,其他任務都會在任務隊列中排隊等候依次執行。newSingleThreadExecutor將所有的外界任務統一到一個線程中支持,所以在這個任務執行之間我們
不需要處理線程同步的問題

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

四種線程池的使用

  下面我們就來看一下對於上面四種線程池是如何使用的。

Runnable command = new Runnable() {
    public void run() {
        //doSomething
    }
};

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
fixedThreadPool.execute(command);

ExecutorService  cachedThreadPool= Executors.newCachedThreadPool();
cachedThreadPool.equals(command);

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
//1000毫秒後執行coommand
scheduledThreadPool.schedule(command, 1000, TimeUnit.MILLISECONDS);
//延時5毫秒後,每隔100毫秒執行一次command
scheduledThreadPool.scheduleAtFixedRate(command, 5, 100, TimeUnit.MILLISECONDS);

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(command);



本文參考:http://blog.csdn.net/ljd2038/article/details/51278508

發佈了6 篇原創文章 · 獲贊 9 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章