多線程編程是業務開發中要用到的一項技術,盡管面臨著一些挑戰,但多線程開發也有著很多優點,例如資源利用率更好,程序設計在某些情況下更簡單,程序響應更快等等。接下來主要說一下線程的幾種創建方式以及線程池的應用。
1通過繼承Thread的方式來創建線程
2通過實現Runnable接口來創建線程
這兩種方式本質上其實是一樣的,都是通過run()方法,深挖源碼的話,第一種方式中Thread其實也是實現了Runnable接口,然后重寫run()方法。然而這兩種方式有一個不足,就是run()方法里并不能返回一個結果和拋出異常,所以就有了第三種創建線程的方式,通過Callable接口,并用FutureTask來接收返回的對象。
3通過Callable接口,并用FutureTask來接收返回的對象
和Runnable接口不一樣,Callable接口提供了一個call()方法作為線程執行體,call()方法比run()方法功能要強大。call()方法可以有返回值,call()方法可以聲明拋出異常
Java5提供了Future接口來代表Callable接口里call()方法的返回值,并且為Future接口提供了一個實現類FutureTask,這個實現類既實現了Future接口,還實現了Runnable接口,因此可以作為Thread類的target。在Future接口里定義了幾個公共方法來控制它關聯的Callable任務。
接下來說線程池,線程池是管理線程的地方,在一個項目中,我們知道需要多次的創建線程和銷毀線程,然而創建和銷毀是及其耗費內存資源的,所以就有了線程池的需要,來幫我們管理調度線程,省下了很多的內存資源。
我們應該如何創建一個線程池那?Java中已經提供了創建線程池的一個類:Executor
而我們創建時,一般使用它的子類:ThreadPoolExecutor。但是從ThreadPoolExecutor到Executor并不是簡單的實現或繼承關系,兩者中間有好多中間類。下面是各類之間的層級關系調用實現類圖
我把這幾個類用黑色線條畫了起來,發現我們需要的ThreadPoolExecutor首先繼承了抽象類AbstractExecutorService,然后AbstractExecutorService實現了ExecutorService接口,最ExecutorService接口又實現了Executor接口。
我們再來看一下ThreadPoolExecutor構造函數的參數
我們可以看出,線程池中的corePoolSize就是線程池中的核心線程數量,這幾個核心線程,只是在沒有用的時候,也不會被回收,maximumPoolSize就是線程池中可以容納的最大線程的數量,而keepAliveTime,就是線程池中除了核心線程之外的其他的最長可以保留的時間,因為在線程池中,除了核心線程即使在無任務的情況下也不能被清除,其余的都是有存活時間的,意思就是非核心線程可以保留的最長的空閑時間,而util,就是計算這個時間的一個單位,workQueue,就是等待隊列,任務可以儲存在任務隊列中等待被執行,執行的是FIFIO原則(先進先出)。threadFactory,就是創建線程的線程工廠,最后一個handler,是一種拒絕策略,我們可以在任務滿了之后,拒絕執行某些任務,具體的拒絕策略如下:
ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務(重復此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
再來看線程池的執行流程
我們可以看出,任務進來時,首先執行判斷,判斷核心線程是否處于空閑狀態,如果不是,核心線程就先就執行任務,如果核心線程已滿,則判斷任務隊列是否有地方存放該任務,若果有,就將任務保存在任務隊列中,等待執行,如果滿了,在判斷最大可容納的線程數,如果沒有超出這個數量,就開創非核心線程執行任務,如果超出了,就調用handler實現拒絕策略。
下面一段測試代碼來說明這個線程池執行的流程
最后運行結果與執行流程是一樣的,都是先判斷核心線程,當核心線程最大后,在判斷隊列,當隊列也滿后,在進入非核心線程。