Java:看一波線程池,反正也不虧

前言

線程池在Java併發編程中,有着舉足輕重的位置,學習和掌握它是學習Java的重中之重。反正有空看看,學點知識,又不虧。編程

在開發中,合理使用線程池能帶來什麼好處呢?數組

  • 提升響應速度。當任務到達時,線程已經創建好,當即執行。
  • 下降資源消耗。經過重複使用線程下降新建和銷燬線程帶來的開銷。
  • 提升線程的可管理性。線程池能夠進行統一分配、調優和監控線程的狀況,對資源的管控。

隊列

線程池內部持有一個用於存儲工做任務的隊列,在覈心線程滿了時候,會將任務存儲到隊列中。經常使用隊列類型:bash

  • LinkedBlockingQueue:是基於鏈表結構的有界阻塞隊列 。
  • ArraryBlockingQueue:是基於數組結構的有界阻塞隊列。
  • SynchronousQueue:一個不存儲元素的阻塞隊列。每一個插入操做必須等到一個移除操做,下一個插入操做才能進行。
  • PriorityBlocking:具備優先級的無界阻塞隊列。
  • DelayQueue:延時的無界阻塞隊列。

線程池的使用

  1. 建立
ThreadPoolExecutor( int corePoolSize,
                    int maximumPoolSize,
                    long keepAliveTime,
                    TimeUnit unit, 
                    BlockingQueue<Runnable> workQueue, 
                    ThreadFactory threadFactory,
                    RejectedExecutionHandler handler)
  
複製代碼

在線程的構造方法中,共有5個參數:併發

  • corePoolSize:線程池核心線程的數量。當線程池的線程數量未達到核心線程數量,每提交一個任務都會建立一個線程,直到線程數量等於核心線程數量。核心線程指一直存活在線程池中,不會被會銷燬(可設置超時銷燬),直到線程池關閉。而超過corePoolSize數量建立的線程的就是非核心線程,在空閒keepAliveTime時間後被銷燬。
  • maximumPoolSize:線程池最大線程數量。核線程最大數量=核心線程數量+非核心檢查數量。使用無限的任務隊列(如PriorityBlocking),會致使該參數失效。
  • keepAliveTime:非核心線程閒置時長。閒置超過該時長,非核心線程會被回收。
  • unit:keepAliveTime的時間單位。
  • workQueue:工做隊列,如前文講到的四種隊列。
  • threadFactory:用於建立線程的工廠。能夠經過Executors.defaultThreadFactory();得到默認的線程工廠。主要做用就是給線程起個名字而已。
  • handler:飽和策略。當隊列和線程池都滿了,此時該怎麼處理新任務?ThreadPoolExecutor內有四個內部類實現的策略供選擇。AbortPolicy:直接拋出異常;DiscardPolicy:不處理,拋棄新任務;DiscardOldestPolicy:拋棄隊列總最近的一個隊列;CallerRunsPolicy:使用調用者所在線程執行新任務。默認AbortPolicy策略。
  1. 提交任務到線程池
//execute()方法執行任務
executor.execute(new Runnable() {
	@Override
	public void run() {
		// TODO Auto-generated method stub
	}
});
//submit()方法提交任務
executor.execute(new Runnable() {
	@Override
	public void run() {
	// TODO Auto-generated method stub
	}
});
複製代碼

提交到線程池中有種方式:execute()方法和submit()方法。ide

execute()方法提交的任務沒法得到返回值,沒法判斷提交狀態。 submit()方法能夠得到返回Future類型的對象,根據Future對象能夠判斷任務是否執行成功和經過get()方法得到返回值,但get()方法會阻塞當前線程一段時間。學習

  1. 線程池的關閉
  • shutdown() 將線程池的狀態設置爲SHUTDOWN狀態。而後中斷全部沒有正在執行任務的線程。
  • shutdownNow() 將線程池的狀態設置爲STOP狀態。嘗試中止全部正在執行或者暫停任務的線程,並返回等待執行任務的列表。

因爲二者都是遍歷線程池中的工做線程,而後中斷線程,因此沒法響應中斷的線程可能永遠沒法終止。ui

線程池的實現原理

當咱們提交一個新任務到線程池時,任務在線程池的流程是怎樣的呢?spa

  1. 核心線程是否已滿。核心線程大於等於corePoolSize,表示已滿。不是的話,則會建立新的核心線程執行新任務(只要核心線程未達到數量corePoolSize,都會新建)。否則執行下一步驟。
  2. 工做隊列是否已經滿。在有界隊列中,存儲任務的數量有限。若是任務未滿了,新提交任務存儲到工做隊列。不然進入下個流程。
  3. 非核心線程是否已滿。雖然核心線程隊列已滿,但非核心線程不必定滿了。非核心線程大於maximumPoolSize,表示已滿,線程池拒絕新任務,交給飽和策略處理。不然新建非核心線程處理新任務。

任務隊列的任務何時被處理?線程

線程池中線程處理任務有兩種方式:一種就是新建線程處理任務,另外一種就是循環從阻塞隊列獲取任務來執行。code

四種線程池

有時咱們僅僅是使用一下線程池,不會本身定製線程池,畢竟線程池的構造方法參數那麼多,個人媽耶。那看看類Executors提供的四個線程池。

FixedThreadPool

//建立FixedThreadPool線程池
    Executors.newFixedThreadPool(nThreads); 
    
    //newFixedThreadPool方法的實現
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

複製代碼

建立一個FixedThreadPool線程池,線程的核心線程coreThreadSize和線程池線程容量maximumPoolSize都爲nThreads。因爲使用的無界的LinkedBlockingQueue隊列,將致使maximumPoolSize參數失效,隊列對任務未來者不拒。這裏將保活時間設爲0,意味着空線程會被當即終止。

CachedThreadPool

//建立CachedThreadPool線程池
    Executors.newCachedThreadPool();
    
    //newCachedThreadPool方法的實現
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
複製代碼

CachedThreadPool的核心線程爲0,線程池容量爲Integer.MAX_VALUE,意味着maximumPoolSize是無界的。保活時間keepAliveTime設爲60秒,空閒線程閒置60秒後被終止。因爲使用了沒有容量的SynchronousQueue隊列,意味當提交一個新任務到線程池中,沒有空閒線程來對接,就會新建新的線程來處理新任務。

SingleThreadExecutor

//建立SingleThreadExecutor線程池
    Executors.newSingleThreadExecutor();
    
    //SingleThreadExecutor方法的實現
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
複製代碼

能夠看到SingleThreadExecutor能夠看作定製化的FixedThreadPool,將nThreads置爲1,將核心線程和線程池容量設爲1,以保證只有一個線程在執行。

ScheduledThreadPool

//建立ScheduledThreadPool線程池
    Executors.newScheduledThreadPool(corePoolSize);
    
    //newScheduledThreadPool方法的實現
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    //ScheduledThreadPoolExecutor方法的實現
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
複製代碼

newScheduledThreadPool方法最終會調用ThreadPoolExecutor的構造來建立線程池。將定製化DelayQueue後的DelayedWorkQueue做爲工做隊列。DelayedWorkQueue隊列會把執行時間小的任務排在前面優先執行。若是執行時間相同,就會優先執行提交時間早的任務。

FutureTask

在經過submit()方法提交任務到線程池,會返回有結果的Future類型的對象。Future是一個接口,FutureTask繼承它,因此FutureTask也能夠做爲submit()方法得返回值。同時,FutureTask繼承Runnable接口,這樣又能夠做爲任務提交到線程池中,由調用線程直接執行。

當調用FutureTask的get()方法,若是FutureTask處於已完成狀態(執行完畢),則會致使調用線程當即放回或者拋出異常。不然會使調用線程阻塞。

當調用FutureTask的cancel()方法,若是FutureTask處於未啓動狀態(爲執行run方法),則該任務不會被執行;若是處於啓動狀態,會嘗試以中斷來嘗試中止任務。若是已經完成狀態,則返回false。

總結

經過對線程池知識點理解,能夠清晰掌握建立線程池騷姿式和內涵。覺得實戰提供必要的理論知識。

點個贊,老鐵

若是以爲文章有用,給文章點個贊,鐵子

本文是我的學習總結和知識備忘,如知識有誤或片面,請多加指正,謝謝

知識來源《Java併發編程的藝術》& 互聯網 & 《Java併發編程實戰》

相關文章
相關標籤/搜索