線程的3種實現方式並深刻源碼簡單分析實現原理

前言

本文介紹下線程的3種實現方式並深刻源碼簡單的闡述下原理
複製代碼

三種實現方式

Thread

Runnable

Callable&Future

深刻源碼簡單刨析

Thread

Thread類實現了Runnable接口
複製代碼

枚舉類 State

JVM中的線程必須只能是以上6種狀態的一種。這些狀態是JVM狀態並不能和操做系統線程狀態互相映射
複製代碼
  • NEW
線程剛建立,還未執行(start方法)
複製代碼
  • RUNNABLE
已就緒可運行的狀態。
處於此狀態的線程是正在JVM中運行的, 但可能在等待操做系統級別的資源,例如CPU時間片 複製代碼
  • BLOCKED
阻塞等待監視器鎖
處於此狀態的線程正在阻塞等待監視器鎖, 以進入一個同步塊/方法, 或者在執行完wait()方法後重入同步塊/方法 複製代碼
  • WAITING
等待
執行完Object.wait無超時參數操做, 或者 Thread.join無超時參數操做(進入等待指定的線程執行結束), 或者 LockSupport.park操做後,線程進入等待狀態。 通常在等待狀態的線程在等待其它線程執行特殊操做, 例如:等待另其它線程操做Object.notify()喚醒或者Object.notifyAll()喚醒全部。 複製代碼
  • TIMED_WAITING
限時等待
Thread.sleep、Object.wait帶超時時間、 Thread.join帶超時時間、 LockSupport.parkNanos、 LockSupport.parkUntil這些操做會時線程進入限時等待 複製代碼
  • TERMINATED
終止,線程執行完畢
複製代碼

線程狀態流轉

JVM 線程狀態流轉圖java

注意:不要混淆操做系統線程狀態和java線程狀態。JVM中的線程必須只能是以上6種狀態的一種!RUNNABLE = 正在JVM中運行的(Running)+ 可能在等待操做系統級別的資源(Ready)例如CPU時間片web

  • 線程建立以後,不會當即進入就緒狀態,由於線程的運行須要一些條件(好比內存資源),編程

  • 只有線程運行須要的全部條件知足了,才進入就緒狀態。緩存

  • 當線程進入就緒狀態後,不表明馬上就能獲取CPU執行時間,也許此時CPU正在執行其餘的事情,所以它要等待。安全

  • 當獲得CPU執行時間以後,線程便真正進入運行狀態。多線程

  • 線程在運行狀態過程當中,可能有多個緣由致使當前線程不繼續運行下去,好比用戶主動讓線程睡眠(睡眠必定的時間以後再從新執行)、用戶主動讓線程等待,或者被同步塊給阻塞,此時就對應着多個狀態:time waiting(睡眠或等待必定的事件)、waiting(等待被喚醒)、blocked(阻塞)。編輯器

  • 當因爲忽然中斷或者子任務執行完畢,線程就會被消亡。函數式編程

關鍵屬性

  • name
表示Thread的名字,
能夠經過Thread類的構造器中的參數來指定線程名字 複製代碼
  • priority
線程的優先級(最大值爲10,最小值爲1,默認值爲5)
複製代碼
  • daemon
線程是不是守護線程,若是在main線程中建立了一個守護線程,當main方法運行完畢以後,守護線程也會隨着消亡。在JVM中,垃圾收集器線程就是守護線程
複製代碼
  • target
要執行的任務
複製代碼
  • group
線程羣組
複製代碼

關鍵方法

  • start
start()用來啓動一個線程,當調用start方法後,系統纔會開啓一個新的線程來執行用戶定義的子任務,在這個過程當中,會爲相應的線程分配須要的資源
複製代碼
  • run
run()方法是不須要用戶來調用的,當經過start方法啓動一個線程以後,當線程得到了CPU執行時間,便進入run方法體去執行具體的任務。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務
複製代碼
  • sleep

有2個重載版本函數

public static native void sleep(long millis) throws InterruptedException;
 public static void sleep(long millis, int nanos) throws InterruptedException; 複製代碼
sleep讓線程睡眠,交出CPU,讓CPU去執行其餘的任務。
sleep方法不會釋放鎖,也就是說若是當前線程持有對某個對象的鎖, 則即便調用sleep方法,其餘線程也沒法訪問這個對象。 sleep方法至關於讓線程進入阻塞狀態。 複製代碼
  • yield
調用yield方法會讓當前線程交出CPU權限,讓CPU去執行其餘的線程。
它跟sleep方法相似,一樣不會釋放鎖。 可是yield不能控制具體的交出CPU的時間, 另外,yield方法只能讓擁有相同優先級的線程有獲取CPU執行時間的機會。  注意,調用yield方法並不會讓線程進入阻塞狀態, 而是讓線程重回就緒狀態,它只須要等待從新獲取CPU執行時間, 這一點是和sleep方法不同的。 複製代碼
  • join

有三個重載版本flex

join()
join(long millis) //參數爲毫秒 join(long millis,int nanoseconds) //第一參數爲毫秒,第二個參數爲納秒  能夠看出,當調用thread.join()方法後,main線程會進入等待,而後等待thread執行完以後再繼續執行  複製代碼
join方法實際上調用的是Object的wait方法
wait方法會讓線程進入阻塞狀態,而且會釋放線程佔有的鎖,並交出CPU執行權限。 複製代碼
  • interrupt
nterrupt,中斷。
單獨調用interrupt方法可使得處於阻塞狀態的線程拋出一個異常, 也就說,它能夠用來中斷一個正處於阻塞狀態的線程 複製代碼
  • stop
stop方法已是一個廢棄的方法,它是一個不安全的方法。
由於調用stop方法會直接終止run方法的調用,而且會拋出一個ThreadDeath錯誤, 若是線程持有某個對象鎖的話,會徹底釋放鎖,致使對象狀態不一致。 因此stop方法基本是不會被用到的。 複製代碼

Runnable

函數式編程 FunctionInterface

該註解不是必須的,若是一個接口符合"函數式接口"定義,那麼加不加該註解都沒有影響。
加上該註解可以更好地讓編譯器進行檢查。 若是編寫的不是函數式接口,可是加上了@FunctionInterface,那麼編譯器會報錯 複製代碼
// 正確的函數式接口
@FunctionalInterface public interface TestInterface {   // 抽象方法  public void sub();   // java.lang.Object中的public方法  public boolean equals(Object var1);   // 默認方法  public default void defaultMethod(){   }   // 靜態方法  public static void staticMethod(){   } }  // 錯誤的函數式接口(有多個抽象方法) @FunctionalInterface public interface TestInterface2 {   void add();   void sub(); } 複製代碼

方法修飾符 default

目的是讓接口能夠擁有具體的方法,讓接口內部包含了一些默認的方法實現
 若是一個類、類屬變量及方法沒有用任何修飾符 (即沒有用public、protected及private中任何一種修飾) 則其訪問權限爲default(默認訪問權限) 複製代碼

Callable&Future

Callable

Future

  • cancel(boolean mayInterruptIfRunning)
取消任務
參數:是否當即中斷任務執行,或者等等任務結束 複製代碼
  • isCancelled()
任務是否已經取消,若已取消,返回true
 若計算尚未開始,它被取消且再也不開始。 若計算處於運行之中,那麼若是mayInterrupt參數爲true,它就被中斷。 複製代碼
  • isDone()
任務是否已經完成。包括任務正常完成、拋出異常或被取消,都返回true
複製代碼
  • V get() throws InterruptedException, ExecutionException
等待任務執行結束,得到V類型的結果。
InterruptedException: 線程被中斷異常, ExecutionException: 任務執行異常, 若是任務被取消,還會拋出CancellationException 複製代碼
  • V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
參數timeout指定超時時間,uint指定時間的單位,在枚舉類TimeUnit中有相關的定義。
若是計算超時,將拋出TimeoutException  設置了超時時間能夠防止程序無限制的等待future的返回結果 複製代碼

線程池

核心類

Executor
Executor類族是基於生產者-消費者思想設計的,
提交任務至關於生產者生產任務, 而執行任務至關於消費者消費任務 複製代碼
ExecutorService
繼承Executor 添加了一些用於生命週期管理的方法
複製代碼
  • shutdown()
將執行平緩的關閉過程:再也不接受新的任務,而且等待已經提交的任務完成
複製代碼
  • shutdownNow()
表示當即關閉
複製代碼
  • isShutdown和isTerminated
判斷是否關閉或者終止
複製代碼
  • 三個submit
用於提交的任務,能夠用Runnable做爲任務,也能夠用Callable做爲任務
複製代碼
  • 工廠類Executors
用於建立線程池
複製代碼

一、newFixedThreadPool

固定長度線程池
每當提交任務建立一個線程,直到線程池的最大數量, 該方法返回的是ExecutorService對象,能夠看出該方法調用了ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()),其中  第一個參數表示核心線程數,即初始化建立的線程數; 第二個參數是若是最大線程數,即當任務比較多的時候,會自動增長線程數,但不得超過該值; 第三個參數表示等待時間,當超過該時間線程沒收到任務則會收回線程; 第四個參數是第三個參數的單位; 最後一個參數是一個阻塞隊列,用於存放工做任務  複製代碼

二、newSingleThreadExecutor

單線程,這裏調用了Executors工廠類的靜態方法newSingleThreadExecutor,
建立ThreadPoolExecutor對象時,將線程數限制在1便可。 其它如固定長度線程池,只不過是最多隻有一個線程工做 複製代碼

三、newCachedThreadPool

建立一個可緩存的線程池,
若是線程池規模過大,回收空閒線程, 若是需求增長,則添加新的線程。 返回的是 new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>()); 從返回的對象能夠看出,線程初始值是0,最大值是Integer.MAX_VALUE,因此可以使得線程數是動態變化的。 複製代碼

四、newScheduledThreadPool

建立固定長度的線程池,以延遲或定時的方式來執行任務,返回ScheduledThreadPoolExecutor對象
複製代碼

經過以上四種靜態方法構造的線程池中,主要涉及到兩個線程池類,一個是ScheduledThreadPoolExecutor,一個是ThreadPoolExecutor

a、ScheduledThreadPoolExecutor

b、ThreadPoolExecutor

小結

Executor爲了解決多線程任務調度的問題,能夠合理平衡系統吞吐量與內存管理。
Executor是一個最頂層的接口,爲了更好管理Executor的生命週期, 便擴展了ExceutorService接口,該類有具體實現類ThreadPoolExecutor和ScheduledExecutorService。 並經過工廠類Executors的幾個靜態方法來建立不一樣功用的線程池 複製代碼

本文使用 mdnice 排版

相關文章
相關標籤/搜索