Executors的四種線程池

一、Java 線程池

Java通過Executors提供四種線程池,分別爲:
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

(1). newCachedThreadPool

創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。示例代碼如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class ThreadClass {
    public static void main(String[] args){
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cachedThreadPool.execute(() -> System.out.println(index));
        }
        cachedThreadPool.shutdown();
    }
}

線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。

(2). newFixedThreadPool

創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。示例代碼如下:

public static void fixedThreadPoolTest() {
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
        final int index = i;
        fixedThreadPool.execute(()->{
            try{
                Thread.sleep(1000);
                System.out.println(index);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });
    }
    fixedThreadPool.shutdown();
}

因爲定長線程數3,sleep了1000ms所以每創建三個線程會停頓一下

(3) newScheduledThreadPool

創建一個定長線程池,支持定時及週期性任務執行。

這裏想着重說下這個線程池,因爲爲了搞懂和看到預期效果,這個線程池花費了我較多時間。

首先scheduleWithFixedDelay方法,它的參數分別是創建的線程,初始延時時間,線程每次延時的時間,以及時間單位。

public static void  scheduledThreadPool(){
    ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
 
    ((ScheduledExecutorService) scheduledThreadPool).scheduleWithFixedDelay(()-> { //初始延遲5s,之後每個線程延時2s
        try{
            Thread.sleep(3000);
            System.out.println(df.format(new Date()) +" : "+ Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    } ,0,1, TimeUnit.SECONDS);
 
    for(int i=0;i<10;++i){ //延遲50s後再關閉線程池,避免主線程在創建完線程後直接關閉線程池。
        try {
            Thread.sleep(5000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    scheduledThreadPool.shutdown();
 
}

打印結果
2018-12-11 15:12:39 : pool-1-thread-1
2018-12-11 15:12:43 : pool-1-thread-1
2018-12-11 15:12:47 : pool-1-thread-2
2018-12-11 15:12:51 : pool-1-thread-1
2018-12-11 15:12:55 : pool-1-thread-3
2018-12-11 15:12:59 : pool-1-thread-2
2018-12-11 15:13:03 : pool-1-thread-2
2018-12-11 15:13:07 : pool-1-thread-1
2018-12-11 15:13:11 : pool-1-thread-5
2018-12-11 15:13:15 : pool-1-thread-3
2018-12-11 15:13:19 : pool-1-thread-3
2018-12-11 15:13:23 : pool-1-thread-4
2018-12-11 15:13:27 : pool-1-thread-4

從結果看出,它是每四秒執行一次。閱讀代碼,每個線程內部睡3s,延時時間是1s,它的效果就是等待上個線程執行完且延時時間過了再執行自己;

可用場景比如說,地鐵每次到達一個站點花費時間都不同,到達站點後開門,然後必須等待一個固定時間後纔去關門準備去下個站點。當然這種情況要把線程核心數設置爲1.

與之對應的還有一個scheduleAtFixedRate()方法,和上個方法參數相同。不同的是執行會根據線程執行時間和自己設置的延時時間較大值進行。就是說下一個線程必須等待上一個線程執行時間和手動設置延時時間的最大值時間後才能執行自己。

public static void  scheduledThreadPool(){
    ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
 
    ((ScheduledExecutorService) scheduledThreadPool).scheduleAtFixedRate(()-> { //初始延遲5s,之後每個線程延時2s
        try{
            Thread.sleep(500);
            System.out.println(df.format(new Date()) +" : "+ Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    } ,0,8, TimeUnit.SECONDS);
 
    for(int i=0;i<10;++i){ //延遲50s後再關閉線程池
        try {
            Thread.sleep(5000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    scheduledThreadPool.shutdown();
 
}

打印結果
2018-12-11 15:32:28 : pool-1-thread-1
2018-12-11 15:32:36 : pool-1-thread-1
2018-12-11 15:32:44 : pool-1-thread-2
2018-12-11 15:32:52 : pool-1-thread-1
2018-12-11 15:33:00 : pool-1-thread-3
2018-12-11 15:33:08 : pool-1-thread-2
2018-12-11 15:33:16 : pool-1-thread-4

和預期效果相同,另外補充的是這些這個線程池每次只有一個線程執行當前任務,其他線程需要等待時間後才能執行。因爲前一個線程沒有執行完,下一個線程就調不起來。

採用等待Max(線程任務時間,每次等待時間),可以保證和scheduleWithFixedDelay一樣下個線程執行的時候前一個必定執行完成。但如果時間設置的不合理話,可能會造成時間浪費的情況。

該線程池可用於定點執行任務的情況,比如有些任務我就想每隔一段時間執行一次。比如系統發火車票,希望每天的8:00,12:00,16:00…每隔4小時發售一批火車票這種情況。

(4)、newSingleThreadExecutor

創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。示例代碼如下:

public static void singleThreadExecutor(){
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        singleThreadExecutor.execute(()->{
            System.out.println(index);
        });
    }
}

結果中index按序輸出,並不會出現因爲線程串行造成無序情況

二、new Thread的弊端

普通小白的話就是

new Thread(new Runnable() {
	@Override public void run() { 
	// TODO Auto-generated method stub 
	}
 }).start();

那你就out太多了。

  1. new Thread的弊端如下:
    每次new Thread新建對象性能差。
    線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能佔用過多系統資源導致死機或oom。
    缺乏更多功能,如定時執行、定期執行、線程中斷。

  2. 相比new Thread,Java提供的四種線程池的好處在於:
    重用存在的線程,減少對象創建、消亡的開銷,性能佳。
    可有效控制最大併發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
    提供定時執行、定期執行、單線程、併發數控制等功能。

三、爲什麼要用線程池:

1.減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
Java裏面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。

最後,有什麼錯誤歡迎指正。

有什麼問題,歡迎提問。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章