一、線程池簡介
周所周知,Java創建一個新線程的成本是比較高的。因此在面臨大量的多線程任務時,採用線程池幾乎成了慣用的做法,線程池其實也是設計模式中享元模式思想的一種應用。
一般線程池剛啓動時會新建大量的(跟傳入參數有關)空閒線程,程序將一個Runnable或者Callable對象傳給線程池時,線程池會調用空閒線程執行他們的run()方法或者call()方法。執行完成後並不回收該線程,而是再次返回線程池中稱謂空閒線程,等待下一次任務。
二、Java線程池
Java 5以前,開發者需要手動實現線程池,從Java 5 開始,Java支持了內建線程池,提供了一個Executors類來新建線程池,它位於java.util.concurrent包下,基本上所有跟併發編程相關的都在該包下面。
Java 常用的線程池有7種,他們分別是:
(1)newCachedThreadPool :創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
(2)newFixedThreadPool:創建一個固定數目的、可重用的線程池。
(3)newScheduledThreadPool:創建一個定長線程池,支持定時及週期性任務執行。
(4)newSingleThreadExecutor:創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
(5)newSingleThreadScheduledExcutor:創建一個單例線程池,定期或延時執行任務。
(6)newWorkStealingPool:創建持有足夠線程的線程池來支持給定的並行級別,並通過使用多個隊列,減少競爭,它需要穿一個並行級別的參數,如果不傳,則被設定爲默認的CPU數量。
(7) ForkJoinPool:支持大任務分解成小任務的線程池,這是Java8新增線程池,通常配合ForkJoinTask接口的子類RecursiveAction或RecursiveTask使用。
三、示例代碼(只介紹代表性的幾個)
(1)newCachedThreadPool 會根據任務來臨的需要決定是否創建新的線程,也就是如果來了新任務又沒有空閒線程,它就會新建一個線程,下面用代碼可以理解這個事情。
package com;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService m=Executors.newCachedThreadPool();
for(int i=1;i<=10;i++){
final int count=i;
m.submit(new Runnable(){
@Override
public void run() {
System.out.println("線程:"+Thread.currentThread()+"負責了"+count+"次任務");
}
});
//下面這行代碼註釋的話,線程池會新建10個線程,不註釋的話,因爲會複用老線程,不會產生10個線程
// Thread.sleep(1);
}
}
}
線程:Thread[pool-1-thread-1,5,main]負責了1次任務
線程:Thread[pool-1-thread-2,5,main]負責了2次任務
線程:Thread[pool-1-thread-3,5,main]負責了3次任務
線程:Thread[pool-1-thread-4,5,main]負責了4次任務
線程:Thread[pool-1-thread-5,5,main]負責了5次任務
線程:Thread[pool-1-thread-6,5,main]負責了6次任務
線程:Thread[pool-1-thread-7,5,main]負責了7次任務
線程:Thread[pool-1-thread-8,5,main]負責了8次任務
線程:Thread[pool-1-thread-9,5,main]負責了9次任務
線程:Thread[pool-1-thread-10,5,main]負責了10次任務
不註釋掉Thread.sleep(1)的結果如下——產生了2個線程
線程:Thread[pool-1-thread-1,5,main]負責了1次任務
線程:Thread[pool-1-thread-2,5,main]負責了2次任務
線程:Thread[pool-1-thread-2,5,main]負責了3次任務
線程:Thread[pool-1-thread-2,5,main]負責了4次任務
線程:Thread[pool-1-thread-2,5,main]負責了5次任務
線程:Thread[pool-1-thread-2,5,main]負責了6次任務
線程:Thread[pool-1-thread-2,5,main]負責了7次任務
線程:Thread[pool-1-thread-2,5,main]負責了8次任務
線程:Thread[pool-1-thread-2,5,main]負責了9次任務
線程:Thread[pool-1-thread-2,5,main]負責了10次任務
(2)newFixedThreadPool 創建一個固定大小的、可重用是線程池
package com;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService m=Executors.newFixedThreadPool(4);
for(int i=1;i<=10;i++){
final int count=i;
m.submit(new Runnable(){
@Override
public void run() {
System.out.println("線程:"+Thread.currentThread()+"負責了"+count+"次任務");
}
});
Thread.sleep(1000);
}
}
}
結果如下:
線程:Thread[pool-1-thread-1,5,main]負責了1次任務
線程:Thread[pool-1-thread-2,5,main]負責了2次任務
線程:Thread[pool-1-thread-3,5,main]負責了3次任務
線程:Thread[pool-1-thread-4,5,main]負責了4次任務
線程:Thread[pool-1-thread-1,5,main]負責了5次任務
線程:Thread[pool-1-thread-2,5,main]負責了6次任務
線程:Thread[pool-1-thread-3,5,main]負責了7次任務
線程:Thread[pool-1-thread-4,5,main]負責了8次任務
線程:Thread[pool-1-thread-1,5,main]負責了9次任務
線程:Thread[pool-1-thread-2,5,main]負責了10次任務
(3)newScheduledThreadPool創建一個定長線程池,支持定時及週期性任務執行。
package com;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws Exception {
// 指定大小爲4
ScheduledExecutorService m = Executors.newScheduledThreadPool(4);
m.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Date now = new Date();
System.out.println("線程" + Thread.currentThread() + "報時:" + now);
}
}, 1, 1, TimeUnit.SECONDS); // 延遲1s秒執行,每隔1s執行一次
}
}
結果
線程Thread[pool-1-thread-1,5,main]報時:Thu Sep 10 14:55:15 CST 2015
線程Thread[pool-1-thread-1,5,main]報時:Thu Sep 10 14:55:16 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:17 CST 2015
線程Thread[pool-1-thread-1,5,main]報時:Thu Sep 10 14:55:18 CST 2015
線程Thread[pool-1-thread-1,5,main]報時:Thu Sep 10 14:55:19 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:20 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:21 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:22 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:23 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:24 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:25 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:26 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:27 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:28 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:29 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:30 CST 2015
(4)newWorkStealingPool創建一個帶並行級別的線程池,並行級別決定了同一時刻最多有多少個線程在執行,如不穿如並行級別參數,將默認爲當前系統的CPU個數。下面用代碼來體現這種並行的限制,從結果中可以看到,同一時刻只有兩個線程執行。
package com;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws Exception {
// 設置並行級別爲2,即默認每時每刻只有2個線程同時執行
ExecutorService m = Executors.newWorkStealingPool(2);
for (int i = 1; i <= 10; i++) {
final int count=i;
m.submit(new Runnable() {
@Override
public void run() {
Date now=new Date();
System.out.println("線程" + Thread.currentThread() + "完成任務:"
+ count+" 時間爲:"+ now.getSeconds());
try {
Thread.sleep(1000);//此任務耗時1s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
while(true){
//主線程陷入死循環,來觀察結果,否則是看不到結果的
}
}
}
結果:
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:1 時間爲:7
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:2 時間爲:7
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:3 時間爲:8
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:4 時間爲:8
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:5 時間爲:9
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:6 時間爲:9
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:7 時間爲:10
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:8 時間爲:10
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:9 時間爲:11
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:10 時間爲:11
三、總結
以上幾個線程池基本滿足了常用的線程池需求,開發者可根據場景靈活選中,其中可以實現大任務分解的ForkJoinPool線程池的介紹因爲篇幅較長,專門單獨寫了一篇博客,地址是: Java 多線程中的任務分解機制-ForkJoinPool詳解