什么是線程池
學習編程的小伙伴們會經常聽到“線程池”、“連接池”這類的詞語,可是到底“池”是什么意思呢?我講個故事大家就理解了:在很久很久以前有一家銀行,一年之中只有一個客戶來辦理業務,隨着時間的推移,辦理業務的人數每年都增加五千。20年之后這家銀行辦理業務的人次已經到十萬。最開始只有一個客戶的時候銀行只需要雇佣一個按辦理業務次數計工資的臨時工就行了,辦完業務就解雇。隨着辦理業務的人不斷增多,銀行老板發現繼續雇佣按次計費的員工太麻煩了,每天都在招人,又每天都解雇人。所以老板就想出了一個辦法,雇佣幾個員工一直在辦事大廳待命,有顧客來的時候,櫃員就給顧客辦業務,辦完一個之后再繼續為下一個顧客辦業務,如果沒有下一個顧客就繼續待命。就這樣這個“聰明”的老板發明了“櫃員池”。
線程池就是提前創建若干個線程,如果有任務需要處理,線程池里的線程就會處理任務,處理完之后線程並不會被銷毀,而是等待下一個任務。由於創建和銷毀線程都是消耗系統資源的,所以當你想要頻繁的創建和銷毀線程的時候就可以考慮使用線程池來提升系統的性能。
在Java中使用線程池
線程池的種類
Java中有三個比較常用的線程池,分別是FixedThreadPool,SingleThreadExecutor,CachedThreadPool。
1) FixedThreadPool
這是一個線程數固定的線程池,當這個線程池被創建的時候,池里的線程數就已經固定了。當需要運行的線程數量大體上變化不大時,適合使用這種線程池。固定數量還有一個好處,它可以一次性支付高昂的創建線程的開銷,之后再使用的時候就不再需要這種開銷。
2) SingleThreadExecutor
這是一個線程數量為1的線程池,所有提交的這個線程池的任務都會按照提交的先后順序排隊執行。單個線程執行有個好處:由於任務之間沒有並發執行,因此提交到線程池種的任務之間不會相互干擾。程序執行的結果更具有確定性。
3) CachedThreadPool
一看到Cache就知道這是一個和緩存有關的線程池,每次有任務提交到線程池的時候,如果池中沒有空閑的線程,線程池就會為這個任務創建一個線程,如果有空閑的線程,就會使用已有的空閑線程執行任務。有的人可能會有個疑惑:這樣線程不就越來越多了嗎?其實不是的,這個線程池還有一個銷毀機制,如果一個線程60秒之內沒有被使用過,這個線程就會被銷毀,這樣就節省了很多資源。CachedThreadPool是一個比較通用的線程池,它在多數情況下都能表現出優良的性能。以后編碼的時候,遇事不決,用緩存(線程池)。
線程池的使用
上面說了半天也沒說這東西到底咋用,有的小伙伴已經等不及了。其實Java的線程池使用起來很簡單。只需要一個接口和一個類就行:
ExecutorService是所有的線程池都實現了的接口,用這個接口可以定義句柄。
Executors類用於創建不同種類的線程池,Executors.new***()方法就可以返回一個線程池的對象,***表示線程池的名字。
具體代碼如下:
public class CreateThreadPool{
public static void main(String[] args) {
//定義一個有5個線程的線程池
ExecutorService fixedTP = Executors.newFixedThreadPool(5);
ExecutorService singleTE = Executors.newSingleThreadExecutor();
ExecutorService cachedTP = Executors.newCachedThreadPool();
}
}
不同的線程池之間只有定義的方式略有區別,使用方法是完全相同的。下面以緩存線程池為例來介紹線程池的使用方法:
class ExampleThread implements Runnable {
@Override
public void run() {
System.out.println("Do something");
}
}
public class UseThreadPool {
public static void main(String[] args) {
ExecutorService cachedTP = Executors.newCachedThreadPool();
cachedTP.execute(new ExampleThread());
cachedTP.shutdown();
}
}
ExampleThread實現了Runnable接口,這次我們沒有使用Thread類創建線程,而是使用的線程池,直接調用線程池的execute()方法並將ExampleThread的對象傳進去就可以創建線程了,execute()方法可以接收任何實現Runnable接口的類的對象。使用線程池的時候別忘了調用shutdown()方法關閉線程池。繼承自Thread類的對象也可以用同樣的方式創建線程,因為Thread類也實現了Runnable接口。
上次我們還講到了可以通過實現Callable接口創建線程,這個線程就不能使用execute()方法了。線程池還有另一個方法:submit()方法接收實現Callable接口的的對象,同時還有一個重載的方法,重載方法接收實現Runnable方法的類。也就是說不管是實現了Runnable還是Callable的類都可以作為它的參數。submit()方法還有一個Future類型的返回值,Future用於獲取線程的返回值,Future是一個有泛型的類,泛型的類型與Callable的泛型相同。演示代碼如下:
class ExampleThread implements Callable<String> {
@Override
public String call() {
return "Some Value";
}
}
public class UseThreadPool {
public static void main(String[] args) {
ExecutorService cachedTP = Executors.newCachedThreadPool();
Future<String> future = cachedTP.submit(new ExampleThread());
try {
String value = future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
cachedTP.shutdownNow();
}
}
future.get()方法,這個方法會阻塞當前線程,一直等到線程池里相應的線程執行結束的時候當前線程才會解除阻塞。因此,這個get()方法也是只有等到不得不用返回值的時候才會調用,否則會影響程序運行的效率。
shutdown()方法還有一個孿生兄弟shutdownNow(),兩者區別如下:
shutdown():調用這個方法之后就線程池就不會再接收其它任務了。
shutdownNow():調用這個方法會向所有的線程發一個中斷信號(如果線程選擇忽略,這個線程還是會繼續執行),並且不再執行排隊中的任務,將排隊中的任務作為返回值返回(List<Runnable>)。
總結
線程池是一個典型的“用空間換時間”的應用案例,在線程池中始終維護一定數量的線程,這樣不必每次都創建新的線程,代價是線程即使空閑的時候也要占用內存資源。當需要頻繁創建和銷毀線程的時候,使用線程池可以顯著提高系統的運行效率,在線程池的不同種類中,緩存線程池在通常情況下都是性能最好的。在提交任務到線程池的時候,通常實現Runnable接口的使用execute()方法提交,實現Callable接口的使用submit()方法提交。最后,使用線程池后別忘了shutdown()。公眾號:今日說碼。關注我的公眾號,可查看連載文章。遇到不理解的問題,直接在公眾號留言即可。
