前述文章介紹了一些線程相關的知識點,今天我們討論一下線程池,可以說線程池是我們編程人員必不可少的知識點之一;那什麼是線程池呢? 簡單理解其實就是裝線程和任務的一個容器;有了它我們可以最大程度的減少了頻繁的創建和銷燬線程所帶來的資源開銷,其實總的來說就是爲了減小資源的浪費,使效率儘可能的最大化;通常我們常見的大概有兩類線程池ThreadPollExecutor和ForkJoinPoll
線程池相關的幾個類
Executor接口定義了execute()方法;ExecutorService裏定義了線程生命週期相關的方法:
Callable與Future
說到線程池,那麼我們先介紹下Callable與Future接口;說到線程的實現,我們隨口而來的就是繼承Thread、實現Runnable接口重寫run()方法,但其實還有一種方法就是實現Callable接口重寫call()方法
但此時或許大家會有所疑問,爲什麼已經有了Runnable這種實現方式了,還要設計出Callable呢?這是因爲Callable是對Runnable的一種擴展,對Callable的調用可以有返回值;那返回值又該如何操作呢?這就又不得不說到Future了,Future正是用來接收Callable中的call()方法的返回值而出現的,可以說二者相輔相成;在多種線程池的實現裏都用到了Callable和Future,因此我們需要對這兩個接口有所瞭解。
Callable的使用
public class Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> c = new Callable() {
@Override
public String call() throws Exception {
return "Hello Callable";
}
};
ExecutorService service = Executors.newCachedThreadPool();
Future<String> future = service.submit(c); //異步
System.out.println(future.get());//阻塞 獲得call()的返回值
service.shutdown();
}
}
FutureTask
FutureTask是一個集Future和Runnabla特性與一身的一個接口(實現了Future接口和Runnable接口)即可以用來接收一個線程將來的返回值,又可以當做任務來交給其他線程去執行。
示例代碼
public class T06_00_Future {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Integer> task = new FutureTask<>(()->{
TimeUnit.MILLISECONDS.sleep(500);
return 1000;
}); //new Callable () { Integer call();}
new Thread(task).start();
System.out.println(task.get()); //阻塞
}
}
CompletableFuture
這是一個多任務執行協調、規劃和結果處理的類,管理多個Future
示例代碼
/**
* 假設你能夠提供一個服務
* 這個服務查詢各大電商網站同一類產品的價格並彙總展示
*/
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class CompletableFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long start, end;
//單線程執行方式
/*
start = System.currentTimeMillis();
priceOfTM();
priceOfTB();
priceOfJD();
end = System.currentTimeMillis();
System.out.println("use serial method call! " + (end - start));
*/
//使用CompletableFuture
start = System.currentTimeMillis();
CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(()->priceOfTM());
CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(()->priceOfTB());
CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(()->priceOfJD());
//將上面三個異步組織起來,只有當3個都完成時纔會執行下面的代碼
CompletableFuture.allOf(futureTM, futureTB, futureJD).join();
//另一種寫法 將多條步驟統一
/*
CompletableFuture.supplyAsync(()->priceOfTM()) //異步調用priciOfTM()
.thenApply(String::valueOf) //執行完成調用String的valueOf()
.thenApply(str-> "price " + str) //然後拼接“price”+str
.thenAccept(System.out::println); //輸出結果
*/
end = System.currentTimeMillis();
System.out.println("use completable future! " + (end - start));
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
private static double priceOfTM() {
delay();
return 1.00;
}
private static double priceOfTB() {
delay();
return 2.00;
}
private static double priceOfJD() {
delay();
return 3.00;
}
private static void delay() {
int time = new Random().nextInt(500);
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("After %s sleep!\n", time);
}
}
ThreadPollExecutor
public class HelloThreadPool {
static class Task implements Runnable {
private int i;
public Task(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Task " + i);
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "Task{" +
"i=" + i +
'}';
}
}
public static void main(String[] args) {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 8; i++) {
tpe.execute(new Task(i));
}
System.out.println(tpe.getQueue());
tpe.execute(new Task(100));
System.out.println(tpe.getQueue());
tpe.shutdown();
}
}
參數含義
1.核心線程數
2.最大線程數
3.非核心線程存活時間
4.時間單位
5.線程等待隊列
6.線程的創建方式(名稱、優先級等信息)
7.拒絕策略(無空閒線程和隊列滿時,其他任務到來時的拒絕策略)
注:剛開始線程池內線程數爲0,當有任務進來時,纔會創建線程;當任務達到核心線程數時,再有其他任務進來會進入等待隊列,當隊列已滿核心線程也不空閒時,如果還沒有達到最大線程數時,會再創建線程 …
常見的一些ThreadPollExecutor的線程池
JDK提供的一些線程池,底層還是ThreadPollExecutor,只是爲了方便使用又做了一層包裝。
SingleThreadPool
單個線程的線程池,可以保證線程的順序執行;LinkedBlockingQueue實際上並不是無界阻塞隊列,它的大小爲Integer.MAX_VALUE。
CachedThreadPool
緩衝線程池,隊列使用SynchronousQueue(隊列大小爲0,當某個線程想往隊列放數據時,如果沒有線程去拿,那麼放數據的線程阻塞等待,直到有線程來拿;可這麼理解,需要手把手交貨),根據該隊列的特點,每來一個任務,該線程池就會啓動一個線程去執行直到Integer.MAX_VALUE;但一般情況下我們的機器支持不了這麼多線程。使用於短時間內的高併發場景(但高併發也得有個限量)
FixedThreadPoll
固定線程池,使用於長時間高併發場景。
ScheduledThreadPool
適用於執行定時任務的線程池。
ForkJoinPoll類線程池
該類線程池相對於ThreadPollExecutor來說比較複雜;該類線程池設計的思想是將一個大任務分割爲一個個的子任務在不同的線程裏執行,然後彙總。
使用該類線程池,任務要定義爲特定的類型 ForkJoinTask;常用的實現類有RecursiveAction(無返回值的)和RecursiveTask(有返回值的)
具體使用參考
public class ForkJoinPool {
static int[] nums = new int[1000000];
static final int MAX_NUM = 50000;
static Random r = new Random();
//單線程的執行
static {
for(int i=0; i<nums.length; i++) {
nums[i] = r.nextInt(100);
}
System.out.println("---" + Arrays.stream(nums).sum()); //stream api
}
//分成一個個小任務執行
static class AddTask extends RecursiveAction {
int start, end;
AddTask(int s, int e) {
start = s;
end = e;
}
@Override
protected void compute() {
if(end-start <= MAX_NUM) {
long sum = 0L;
for(int i=start; i<end; i++) sum += nums[i];
System.out.println("from:" + start + " to:" + end + " = " + sum);
} else {
int middle = start + (end-start)/2;
AddTask subTask1 = new AddTask(start, middle);
AddTask subTask2 = new AddTask(middle, end);
subTask1.fork();
subTask2.fork();
}
}
}
//有返回值的實現
static class AddTaskRet extends RecursiveTask<Long> {
private static final long serialVersionUID = 1L;
int start, end;
AddTaskRet(int s, int e) {
start = s;
end = e;
}
@Override
protected Long compute() {
if(end-start <= MAX_NUM) {
long sum = 0L;
for(int i=start; i<end; i++) sum += nums[i];
return sum;
}
int middle = start + (end-start)/2;
AddTaskRet subTask1 = new AddTaskRet(start, middle);
AddTaskRet subTask2 = new AddTaskRet(middle, end);
subTask1.fork();
subTask2.fork();
return subTask1.join() + subTask2.join();
}
}
public static void main(String[] args) throws IOException {
/*ForkJoinPool fjp = new ForkJoinPool();
AddTask task = new AddTask(0, nums.length);
fjp.execute(task);*/
ForkJoinPool fjp = new ForkJoinPool();
AddTaskRet task = new AddTaskRet(0, nums.length);
fjp.execute(task);
long result = task.join();
System.out.println(result);
//System.in.read();
}
}
workStealingPoll
ForkJoinPoll的一個實現;該線程池的每個線程都維護一個線程等待隊列,當有線程空閒而其他線程隊列還有任務時,該線程會去其他隊列裏取出任務執行
具體使用參考
public class WorkStealingPool {
public static void main(String[] args) throws IOException {
ExecutorService service = Executors.newWorkStealingPool();
System.out.println(Runtime.getRuntime().availableProcessors());
service.execute(new R(1000));
service.execute(new R(2000));
service.execute(new R(2000));
service.execute(new R(2000)); //daemon
service.execute(new R(2000));
//由於產生的是精靈線程(守護線程、後臺線程),主線程不阻塞的話,看不到輸出
System.in.read();
}
static class R implements Runnable {
int time;
R(int t) {
this.time = t;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(time + " " + Thread.currentThread().getName());
}
}
}