Java是天生就支持並發的語言,支持並發意味著多線程,線程的頻繁創建在高並發及大數據量是非常消耗資源的,因為java提供了線程池。在jdk1.5以前的版本中,線程池的使用是及其簡陋的,但是在JDK1.5後,有了很大的改善。JDK1.5之後加入了java.util.concurrent包,java.util.concurrent包的加入給予開發人員開發並發程序以及解決並發問題很大的幫助。這篇文章主要介紹下並發包下的Executor接口,Executor接口雖然作為一個非常舊的接口(JDK1.5 2004年發布),但是很多程序員對於其中的一些原理還是不熟悉,因此寫這篇文章來介紹下Executor接口,同時鞏固下自己的知識。如果文章中有出現錯誤,歡迎大家指出。
對於數據庫連接,我們經常聽到數據庫連接池這個概念。因為建立數據庫連接時非常耗時的一個操作,其中涉及到網絡IO的一些操作。因此就想出把連接通過一個連接池來管理。需要連接的話,就從連接池裡取一個。當使用完了,就“關閉”連接,這不是正在意義上的關閉,只是把連接放回到我們的池裡,供其他人在使用。所以對於線程,也有了線程池這個概念,其中的原理和數據庫連接池是差不多的,因此java jdk中也提供了線程池的功能。
線程池的作用:線程池就是限制系統中使用線程的數量以及更好的使用線程
根據系統的運行情況,可以自動或手動設置線程數量,達到運行的最佳效果:配置少了,將影響系統的執行效率,配置多了,又會浪費系統的資源。用線程池配置數量,其他線程排隊等候。當一個任務執行完畢後,就從隊列中取一個新任務運行,如果沒有新任務,那麼這個線程將等待。如果來了一個新任務,但是沒有空閒線程的話,那麼把任務加入到等待隊列中。
為什麼要用線程池:
Java裡面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。Executors類,提供了一系列工廠方法用於創先線程池,返回的線程池都實現了ExecutorService接口。
比較重要的幾個類:
ExecutorService
真正的線程池接口。
ScheduledExecutorService
能和Timer/TimerTask類似,解決那些需要任務重復執行的問題。
ThreadPoolExecutor
ExecutorService的默認實現。
ScheduledThreadPoolExecutor
繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,周期性任務調度的類實現。
要配置一個線程池是比較復雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在Executors類裡面提供了一些靜態工廠,生成一些常用的線程池。
1. newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
2. newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,在提交新任務,任務將會進入等待隊列中等待。如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程。
3. newCachedThreadPool
public static ExecutorService newCachedThreadPool() public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,
那麼就會回收部分空閒(60秒處於等待任務到來)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池的最大值是Integer的最大值(2^31-1)。
4. newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。
實例:
注:使用了java8的lambda表達式以及stream
1.newSingleThreadExecutor
public class SingleThreadExecutorTest { public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
IntStream.range(0, 5).forEach(i -> executor.execute(() -> { String threadName = Thread.currentThread().getName(); System.out.println("finished: " + threadName); })); try { //close pool executor.shutdown(); executor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } finally { if (!executor.isTerminated()) { executor.shutdownNow(); } } } }
輸出結果:
finished: pool-1-thread-1 finished: pool-1-thread-1 finished: pool-1-thread-1 finished: pool-1-thread-1 finished: pool-1-thread-1
線程名都一樣,說明是同一個線程
2.newFixedThreadPool
public class FixedThreadExecutorTest { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); IntStream.range(0, 6).forEach(i -> executor.execute(() -> { try { TimeUnit.SECONDS.sleep(1); String threadName = Thread.currentThread().getName(); System.out.println("finished: " + threadName); } catch (InterruptedException e) { e.printStackTrace(); } })); //close pool 同上... } }
輸出結果:
finished: pool-1-thread-2 finished: pool-1-thread-3 finished: pool-1-thread-1 finished: pool-1-thread-1 finished: pool-1-thread-2 finished: pool-1-thread-3
只創建了三個線程
3.newCachedThreadPool
public class CachedThreadExecutorTest { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); IntStream.range(0, 6).forEach(i -> executor.execute(() -> { try { TimeUnit.SECONDS.sleep(1); String threadName = Thread.currentThread().getName(); System.out.println("finished: " + threadName); } catch (InterruptedException e) { e.printStackTrace(); } }));
//close pool 同上... } }
輸出結果:
finished: pool-1-thread-1 finished: pool-1-thread-2 finished: pool-1-thread-3 finished: pool-1-thread-6 finished: pool-1-thread-5 finished: pool-1-thread-4
創建了6個線程
4.newScheduledThreadPool
public class ScheduledThreadExecutorTest { public static void main(String[] args) { ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); executor.scheduleAtFixedRate(() -> System.out.println(System.currentTimeMillis()), 1000, 2000, TimeUnit.MILLISECONDS); //close pool 同上 } }
輸出結果:
1457967177735 1457967179736 1457967181735
java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類,因此如果要透徹地了解Java中的線程池,必須先了解這個類。下面我們來看一下ThreadPoolExecutor類的具體實現源碼。
在ThreadPoolExecutor類中提供了四個構造方法:
public class ThreadPoolExecutor extends AbstractExecutorService { ..... public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler); ... }
從上面的代碼可以得知,ThreadPoolExecutor繼承了AbstractExecutorService類,並提供了四個構造器,事實上,通過觀察每個構造器的源碼具體實現,發現前面三個構造器都是調用的第四個構造器進行的初始化工作。
ThreadPoolExecutor的完整構造方法的簽名是:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小時 TimeUnit.MINUTES; //分鐘 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //納秒
ArrayBlockingQueue; //有界隊列 LinkedBlockingQueue; //無界隊列 SynchronousQueue; //特殊的一個隊列,只有存在等待取出的線程時才能加入隊列,可以說容量為0,是無界隊列
ArrayBlockingQueue和PriorityBlockingQueue使用較少,一般使用LinkedBlockingQueue和Synchronous。線程池的排隊策略與BlockingQueue有關。
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。 ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。 ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重復此過程) ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
ThreadPoolExecutor是Executors類的底層實現。
在JDK幫助文檔中,有如此一段話:
“強烈建議程序員使用較為方便的 Executors
工廠方法 Executors.newCachedThreadPool()
(無界線程池,可以進行自動線程回收)、Executors.newFixedThreadPool(int)
(固定大小線程池)Executors.newSingleThreadExecutor()
(單個後台線程)
它們均為大多數使用場景預定義了設置。”
下面介紹一下幾個類的源碼:
ExecutorService newFixedThreadPool (int nThreads):固定大小線程池。
可以看到,corePoolSize和maximumPoolSize的大小是一樣的(實際上,後面會介紹,如果使用無界queue的話 maximumPoolSize參數是沒有意義的),keepAliveTime和unit的設值表明什麼?-就是該實現不想keep alive!最後的BlockingQueue選擇了LinkedBlockingQueue,該queue有一個特點,他是無界的。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
ExecutorService newSingleThreadExecutor():單線程
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
ExecutorService newCachedThreadPool():無界線程池,可以進行自動線程回收
這個實現就有意思了。首先是無界的線程池,所以我們可以發現maximumPoolSize為big big。其次BlockingQueue的選擇上使用SynchronousQueue。可能對於該BlockingQueue有些陌生,簡單說:該 QUEUE中,每個插入操作必須等待另一個線程的對應移除操作。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
從上面給出的ThreadPoolExecutor類的代碼可以知道,ThreadPoolExecutor繼承了AbstractExecutorService,AbstractExecutorService是一個抽象類,它實現了ExecutorService接口。
public abstract class AbstractExecutorService implements ExecutorService
而ExecutorService又是繼承了Executor接口
public interface ExecutorService extends Executor
我們看一下Executor接口的實現:
public interface Executor { void execute(Runnable command); }
到這裡,大家應該明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor幾個之間的關系了。
Executor是一個頂層接口,在它裡面只聲明了一個方法execute(Runnable),返回值為void,參數為Runnable類型,從字面意思可以理解,就是用來執行傳進去的任務的;
然後ExecutorService接口繼承了Executor接口,並聲明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象類AbstractExecutorService實現了ExecutorService接口,基本實現了ExecutorService中聲明的所有方法;
然後ThreadPoolExecutor繼承了類AbstractExecutorService。
在ThreadPoolExecutor類中有幾個非常重要的方法:
public void execute(Runnable command) public <T> Future<T> submit(Callable<T> task) public void shutdown() public List<Runnable> shutdownNow() //返回未執行的任務
execute()方法實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現,這個方法是ThreadPoolExecutor的核心方法,通過這個方法可以向線程池提交一個任務,交由線程池去執行。
submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經有了具體的實現,在ThreadPoolExecutor中 並沒有對其進行重寫,這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果,去看submit()方法的 實現,會發現它實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果(Future相關內容將在下一篇講述)。
shutdown()和shutdownNow()是用來關閉線程池的。