Java中的線程池
綜述
在我們的開發中經常會使用到多線程。例如在Android中,由于主線程的諸多限制,像網絡請求等一些耗時的操作我們必須在子線程中運行。我們往往會通過new Thread來開啟一個子線程,待子線程操作完成以后通過Handler切換到主線程中運行。這么以來我們無法管理我們所創建的子線程,并且無限制的創建子線程,它們相互之間競爭,很有可能由于占用過多資源而導致死機或者OOM。所以在Java中為我們提供了線程池來管理我們所創建的線程。
線程池的使用
采用線程池的好處
在這里我們首先來說一下采用線程池的好處。
1. 重用線程池中已經存在的線程,減少了線程的創建和消亡多造成的性能開銷。
2. 能夠有效控制最大的并發線程數,提高了系統資源的使用率,并且還能夠避免大量線程之間因為相互搶占系統資源而導致阻塞。
3. 能夠對線程進行簡單管理,并提供定時執行、定期執行、單線程、并發數控制等功能。
ThreadPoolExecutor
我們可以通過ThreadPoolExecutor來創建一個線程池。下面我們就來看一下ThreadPoolExecutor中的一個構造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
ThreadPoolExecutor參數含義
1. corePoolSize
線程池中的核心線程數,默認情況下,核心線程一直存活在線程池中,即便他們在線程池中處于閑置狀態。除非我們將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設為true的時候,這時候處于閑置的核心線程在等待新任務到來時會有超時策略,這個超時時間由keepAliveTime來指定。一旦超過所設置的超時時間,閑置的核心線程就會被終止。
2. maximumPoolSize
線程池中所容納的最大線程數,如果活動的線程達到這個數值以后,后續的新任務將會被阻塞。
3. keepAliveTime
非核心線程閑置時的超時時長,對于非核心線程,閑置時間超過這個時間,非核心線程就會被回收。只有對ThreadPoolExecutor的allowCoreThreadTimeOut屬性設為true的時候,這個超時時間才會對核心線程產生效果。
4. unit
用于指定keepAliveTime參數的時間單位。他是一個枚舉,可以使用的單位有天(TimeUnit.DAYS),小時(TimeUnit.HOURS),分鐘(TimeUnit.MINUTES),毫秒(TimeUnit.MILLISECONDS),微秒(TimeUnit.MICROSECONDS, 千分之一毫秒)和毫微秒(TimeUnit.NANOSECONDS, 千分之一微秒);
5. workQueue
線程池中保存等待執行的任務的阻塞隊列。通過線程池中的execute方法提交的Runable對象都會存儲在該隊列中。我們可以選擇下面幾個阻塞隊列。
- ArrayBlockingQueue:基于數組實現的有界的阻塞隊列,該隊列按照FIFO(先進先出)原則對隊列中的元素進行排序。
- LinkedBlockingQueue:基于鏈表實現的阻塞隊列,該隊列按照FIFO(先進先出)原則對隊列中的元素進行排序。
- SynchronousQueue:內部沒有任何容量的阻塞隊列。在它內部沒有任何的緩存空間。對于SynchronousQueue中的數據元素只有當我們試著取走的時候才可能存在。
- PriorityBlockingQueue:具有優先級的無限阻塞隊列。
- 我們還能夠通過實現BlockingQueue接口來自定義我們所需要的阻塞隊列。
6. threadFactory
線程工廠,為線程池提供新線程的創建。ThreadFactory是一個接口,里面只有一個newThread方法。
7. handler
他是RejectedExecutionHandler對象,而RejectedExecutionHandler是一個接口,里面只有一個rejectedExecution方法。當任務隊列已滿并且線程池中的活動線程已經達到所限定的最大值或者是無法成功執行任務,這時候ThreadPoolExecutor會調用RejectedExecutionHandler中的rejectedExecution方法。在ThreadPoolExecutor中有四個內部類實現了RejectedExecutionHandler接口。在線程池中它默認是AbortPolicy,在無法處理新任務時拋出RejectedExecutionException異常。下面是在ThreadPoolExecutor中提供的四個可選值。
- CallerRunsPolicy:只用調用者所在線程來運行任務。
- AbortPolicy:直接拋出RejectedExecutionException異常。
- DiscardPolicy:丟棄掉該任務,不進行處理
- DiscardOldestPolicy:丟棄隊列里最近的一個任務,并執行當前任務。
- 我們也可以通過實現RejectedExecutionHandler接口來自定義我們自己的handler。如記錄日志或持久化不能處理的任務。
ThreadPoolExecutor執行規則
- 如果在線程池中的線程數量沒有達到核心的線程數量,這時候就回啟動一個核心線程來執行任務。
- 如果線程池中的線程數量已經超過核心線程數,這時候任務就會被插入到任務隊列中排隊等待執行。
- 由于任務隊列已滿,無法將任務插入到任務隊列中。這個時候如果線程池中的線程數量沒有達到線程池所設定的最大值,那么這時候就會立即啟動一個非核心線程來執行任務。
- 如果線程池中的數量達到了所規定的最大值,那么就會拒絕執行此任務,這時候就會調用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() {
// doSomething
}
});</code></pre>
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();
}</code></pre>
關閉線程池
我們可以通過shutdown方法或者shutdownNow方法來關閉線程池。對于這兩種關閉線程池的方式他們都是通過遍歷線程池中所有的線程,然后依次調用線程的interrupt方法來中斷線程。當然對于這兩種關閉線程池的方法也是有一定區別的(具體區別見下面注釋)。
當我們調用了下面任何一個關閉方法時,isShutdown方法就會返回true。而當線程池關閉成功以后isTerminaed方法會返回true。對于線程池中的正在執行的任務如果我們希望他們執行完成以后再去關閉線程池則調用shutdown方法;而我們希望在關閉線程池的時候中斷線程池內正在執行的任務,則調用shutdownNow方法。
/* 首先將線程池的狀態設置成SHUTDOWN狀態,然后中斷所 有沒有正在執行任務的線程。 /
executorService.shutdown();
/* 首先將線程池的狀態設置為STOP,然后開始嘗試停止所有的正在 工作或暫停任務的線程 /
executorService.shutdownNow();</code></pre>
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);</code></pre>
總結
對于Java中的線程池概念同樣適用于Android,例如在我們開發一個app時,我們可以創建一個線程池,將所有的子線程任務交由線程池來處理,于是我們便可以通過這個線程池來管理維護我們的子線程。減少了應用的開銷。