線程池的使用和簡單原理

參考文獻:

1 簡介

 除了①線程池,使用線程還有三種方式,分別是 ②繼承Thread類 ③實現Runnable接口④實現Callable接口,這三種方式最後都需要新建和銷燬線程。在實際的高併發場景下,往往線程數多,每個線程執行任務耗時短。上面三種新建線程的方式,新建和銷燬線程的時間消耗經常會比實際執行任務的耗時還要長,導致提交的任務不能立即響應執行,並且新建和銷燬線程往往有一定的資源消耗;其次創建新線程的方式線程缺乏統一管理,容易出現線程阻塞的情況。所以這裏我們引入了一種複用線程執行任務的方式-線程池,減少新建線程的次數。
 首先說說“任務”的概念,實現Runnable或Callable接口創建的對象只能當做一個可以在線程中運行的任務,不是真正意義上的線程,所以最後還需要通過 Thread 來調用。可以說任務是通過線程驅動從而執行的。

2 原理

2.1 內部邏輯結構

2.2 核心參數

參數 意義 說明
corePoolSize 核心線程數 默認情況,核心線程會一直存活,包括空閒狀態
maximumPoolSize (能容納)最大線程數 當已提交的任務數大於等於該值,後面新提交的任務會使用拒絕策略handler
keepAliveTime 非核心線程閒時存活期 超過該時長,處於閒置狀態的非核心線程會被回收(當allowCoreThreadTimeOut(true)時,keepAliveTime也適用於核心線程)
unit keepAliveTime單位 TimeUnit.DAYS; TimeUnit.HOURS;TimeUnit.MINUTES; TimeUnit.SECONDS;TimeUnit.MILLISECONDS;TimeUnit.MICROSECONDS;
workQueue 任務隊列 當corePoolSize個核心線程都運行時,新來的任務就放到這個隊列裏等待執行(就緒狀態)
threadFactor 線程工廠接口 爲線程池創建新線程

ThreadPoolExecutor類是線程池中最重要的類,可以通過在該類實例化時配置不同的參數傳入構造器,來實現自定義線程池;

		// 創建線程池對象,通過 構造方法 配置核心參數
   Executor executor = new ThreadPoolExecutor( CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,
                                     			TimeUnit.SECONDS, sPoolWorkQueue,sThreadFactory );

		// 構造函數
    public ThreadPoolExecutor (int corePoolSize,int maximumPoolSize,long keepAliveTime,
                               TimeUnit unit,BlockingQueue<Runnable workQueue>,
                               ThreadFactory threadFactory )

2.3 任務執行邏輯

 補充說明:
  • 處理任務優先級:核心線程 > 任務隊列 > 非核心線程 > 拒絕策略handler;
  • 非核心線程空閒時間超過存活期keepAliveTime,就會被回收;一般情況,核心線程即使閒置也不會被回收;(除非當allowCoreThreadTimeOut(true)時,keepAliveTime也適用於核心線程)
  • 一般情況,線程池剛創建時,其中是沒有線程的,只有新任務到來,纔會創建線程執行該任務;

​ 對於線程池、核心線程、非核心線程自己的通俗理解:

​ 線程池看成一個公司,核心線程可以看成是看成是公司的正式員工,非核心線程可以看成是實習生;無論是正式員工還是實習生在同一時刻只能執行一個任務。當有新任務時,老闆首先找正式員工幹活;如果正式員工都在幹活時繼續來新任務,那麼多餘任務就放到一個任務隊列裏,等有正式員工閒下來後接着幹新任務;如果任務較多,超出正式員工能處理的數量,這時老闆就考慮招幾個實習生來幹活,實習生處理任務隊列滿了之後新來的任務。如果任務太多了,那麼老闆就不再接單了,拒絕處理新來的任務。

​ 一般情況,正式員工有任務到來就幹活,沒任務就空閒,不會被裁;實習生有任務到來就幹活,沒任務就空閒,空閒的時間太長了,老闆不養閒人,就把這個實習生裁了。

3 基本使用

	 // 1.創建線程池,通過配置核心參數,從而實現自定義線程池
   Executor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,
                                  TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);
    // 注:在Java中,已內置4種常用線程池,下面會詳細說明

		// 2.向線程池提交任務:execute(),傳入Runnable對象
    threadPool.execute(new Runnable() {
            @Override
            public void run() {
                ... // 線程執行任務
            }
        });

	// 3. 關閉線程池shutdown() 
  threadPool.shutdown();

shutdown()shutdownNow()區別:

  • shutdown():等待正在執行任務的線程執行完再關閉線程池;
  • shutdownNow():不等待立即關閉;

4 四類常用線程池

 根據參數的不同配置,Java內置了4種常用線程池,他們的參數已經配置好了:

  • 定長線程池(FixedThreadPool)
  • 定時線程池(ScheduledThreadPool )
  • 緩存線程池(CachedThreadPool)
  • 單例線程池(SingleThreadExecutor)

4.1 定長線程池FixedThreadPool

  • 特點:只有核心線程 & 不會被回收、線程數固定、任務隊列無大小限制;

  • 應用場景:控制線程池最大併發數;

//創建 定長線程池 對象 & 設置線程池線程數固定爲3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//向線程池提交任務:execute()
fixedThreadPool.execute(() -> System.out.println("定長線程池->執行任務啦"));
//關閉線程池
fixedThreadPool.shutdown();

4.2 定時線程池ScheduledThreadPool

  • 特點:核心線程數固定、非核心線程數無限制(閒時立刻回收);

  • 應用場景:執行定時 / 週期性 任務

//創建 定時線程池 對象 & 設置線程池核心線程數固定爲5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//創建好Runnable類線程對象 & 需執行的任務
Runnable task1 = () -> System.out.println("定時線程池->執行任務啦");
// 向線程池提交任務:schedule()
scheduledThreadPool.schedule(task1, 1, TimeUnit.SECONDS); // 延遲1s後執行任務
scheduledThreadPool.scheduleAtFixedRate(task1,10,1000,TimeUnit.MILLISECONDS);// 延遲10ms後、每隔1000ms執行任務
//關閉線程池
scheduledThreadPool.shutdown();

4.3 緩存線程池CachedThreadPool

  • 特點:只有非核心線程、線程數無限制;靈活回收空閒線程(具備超時機制,全部回收時幾乎不佔系統資源);無線程可用時新建線程;
  • 應用場景:執行數量多、耗時少任務
// 創建 可緩存線程池 對象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//向線程池提交任務1:execute()
cachedThreadPool.execute(() -> System.out.println("可緩存線程池-執行任務1"));
//向線程池提交任務2:execute();複用線程
cachedThreadPool.execute(() -> System.out.println("可緩存線程池-執行任務2"));
//關閉線程池
cachedThreadPool.shutdown();

4.4 單例線程池SingleThreadExecutor

  • 特點:只有一個核心線程,保證所有任務按順序在一個線程中執行,不存在線程安全問題;

  • 應用場景:適合單線程任務;不適合必須使用多線程的耗時任務,如數據庫操作、文件操作等;

//創建 單例線程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//向線程池提交任務:execute()
singleThreadExecutor.execute(() -> System.out.println("單例線程池-執行任務啦"));
//關閉線程池
singleThreadExecutor.shutdown();

4.5 對比

線程池類型 池內 線程類型 池內 線程數 處理特點 應用場景 BlockingQueue
定長線程池FixedThreadPool 核心線程 核心線程數固定 ①核心線程不被回收②任務隊列大小無限制 控制線程池最大併發數 LinkedBlockingQueue
定時線程池ScheduledThreadPool 核心線程,非核心線程, 核心線程數固定,非核心線程數無限制 非核心線程閒時立刻回收(keepAliveTime爲0) 執行定時 / 週期性任務 DelayedWorkQueue
緩存線程池CachedThreadPool 非核心線程 非核心線程數無限制 ①有閒置線程,複用線程;否則新建線程;②一般,提交任務立刻執行③非核心線程閒時靈活回收 執行數量多、耗時少任務 SynchronousQueue
單例線程池SingleThreadExecutor 核心線程 1個核心線程 所有任務按FIFO順序在一個線程中執行②不存在線程安全問題 適合單線程任務;不適合必須使用多線程的耗時任務 LinkedBlockingQueue

5 總結

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