1. 程式人生 > >Java執行緒的詳細解讀

Java執行緒的詳細解讀

一:執行緒實現方式

詞典

  • p:程序
  • LWP:輕量級程序
  • K: 核心執行緒

基於核心執行緒實現

       核心執行緒其實就是有作業系統核心支援的執行緒,這種執行緒有作業系統來負責執行緒的切換,核心通過操作排程器對執行緒進行排程;

       程式一般不會直接去使用核心執行緒而是去使用一種核心執行緒的高階介面(輕量級執行緒),輕量級執行緒就是我們通常所說的執行緒,每一個輕量級執行緒都有一個核心執行緒來支撐,因此輕量級執行緒和核心執行緒是一一對應的模型;

       侷限性:

  1. 居於核心執行緒實現,執行緒的操作(建立,析構,同步)都需要核心支援系統開銷大需要使用者態和核心態來回切換
  2. 需要消耗核心資源(如核心的棧空間)

基於使用者執行緒實現

       使用者執行緒的建立、同步、銷戶和排程完全在使用者態中完成,不需要核心的幫助;

如果程式實現得當,這種執行緒不需要切換到核心態,因此操作可以是非常快速且低消耗的,也可以支援規模更大的執行緒數量,部分高效能資料庫中的多執行緒就是由使用者執行緒實現的;

這種程序與使用者執行緒之間1:N的關係稱為一對多的執行緒模型

       優勢:

  1. 不需要核心支援,系統開銷小

侷限性:

  1. 由於沒有核心的支援,執行緒的所有操作(建立,析構,排程,同步)都需要自己來實現,複雜度非常高,基本不可實現

基於使用者執行緒+輕量級程序混合實現

       使用者執行緒還是完全建立在使用者空間中,因此使用者執行緒的建立、切換、析構等操作依然廉價,並且可以支援大規模的使用者執行緒併發。

輕量級程序作為橋樑,可以使用核心提供執行緒排程功能以及處理器對映功能,並且使用者執行緒的系統呼叫要通過輕量級執行緒來完成,大大降低了整個程序被完全阻塞的風險。這種關係為N:M關係,多對多的執行緒模型

執行緒的建立過程

步驟

  1. 呼叫Java執行緒的Start方法,通過JNI呼叫到JVM層
  2. JVM通過pthread_create()建立一個系統核心執行緒,並指定核心執行緒的初始執行地址,即一個方法指標
  3. 在核心執行緒的初始執行方法中,利用
    JavaCalls模組,呼叫java執行緒的run()方法,開始java級別的執行緒執行

Java執行緒排程

執行緒排程是隻作業系統給執行緒分配使用許可權和使用時間,通常可以分為“搶佔式排程” “協同式排程”兩種方式

搶佔式排程:

       每個執行緒將由系統來分配執行時間,執行緒的切換不由執行緒本身來決定(Java中,Thread.yield()可以讓出執行時間,但無法獲取執行時間)。執行緒執行時間系統可控,也不會有一個執行緒導致整個程序阻塞;

協同式排程:

執行緒執行時間由執行緒本身來控制,執行緒把自己的工作執行完之後,要主動通知系統切換到另外一個執行緒上。最大好處是實現簡單,且切換操作對執行緒自己是可知的,沒啥執行緒同步問題。壞處是執行緒執行時間不可控制,如果一個執行緒有問題,可能一直阻塞在那裡;

執行緒的狀態切換

  1. 執行狀態
    1. Runnable包括作業系統執行緒狀態中的Running和Ready,也就是處於此狀態的執行緒有可能正在執行,也有可能等待CPU為它分配執行時間。執行緒物件建立後,其他執行緒呼叫了該物件的start()方法。該狀態的執行緒位於“可執行執行緒池”中,變得可執行,只等待獲取CPU的使用權。即在就緒狀態的程序除CPU之外,其它的執行所需資源都已全部獲得
  2. 阻塞狀態(無限期等待,期限等待,Blocked)
    1. 無限期等待:該狀態下執行緒不會被分配CPU執行時間,要等待被其他執行緒顯式喚醒。如沒有設定timeoutobject.wait()方法和Thread.join()方法,以及LockSupport.park()方法;
    2. 期限等待:不會被分配CPU執行時間,不過無須等待被其他執行緒顯式喚醒,在一定時間之後會由系統自動喚醒。如Thread.sleep(),設定了timeout的object.wait()和thread.join(),LockSupport.parkNanos()以及LockSupport.parkUntil()方法;
    3. Blocked:執行緒被阻塞了。與等待狀態的區別是:阻塞在等待著獲取到一個排他鎖,這個事件將在另外一個執行緒放棄這個鎖的時候發生;而等待則在等待一段時間,或喚醒動作的發生。在等待進入同步區域時,執行緒將進入這種狀態;

執行緒間的協作

       常見的執行緒間通訊有wait、notify、notifyAll、join、yield、sleep

  1. Join
    1. Join是Thread類的一個方法,作用是阻塞呼叫執行緒直到該執行緒結束, 意思是t.join()方法將阻塞呼叫此方法的執行緒直到t執行緒結束,此執行緒再繼續執行, 通常用在main主執行緒內,等待其他執行緒執行完再結束主執行緒;
  2. Wait、notify、notifyAll
    1. Wait方法使執行緒進入等待狀態,notify/notify喚醒執行緒的等待狀態進入阻塞狀態

列子:

執行結果

    1. 根據以上案例需要明白幾個點
      1. 在示例中沒有體現但很重要的是,wait/notify方法的呼叫必須處在該物件的鎖(Monitor)中,也即,在呼叫這些方法時首先需要獲得該物件的鎖。否則會爬出IllegalMonitorStateException異常
      2. 從輸出結果來看,在生產者呼叫notify()後,消費者並沒有立即被喚醒,而是等到生產者退出同步塊後才喚醒執行。(這點其實也好理解,synchronized同步方法(塊)同一時刻只允許一個執行緒在裡面,生產者不退出,消費者也進不去)
      3. 消費者被喚醒後是從wait()方法(被阻塞的地方)後面執行,而不是重新從同步塊開頭
  1. Yield
    1. sleep 方法使當前執行中的執行緒睡眠一段時間,進入不可以執行狀態,這段時間的長短是由程式設定的,yield方法使當前執行緒讓出CPU佔有權,但讓出的時間是不可設定的。
    2. yield()也不會釋放鎖標誌。
    3. 實際上,yield()方法對應瞭如下操作;先檢測當前是否有相同優先順序的執行緒處於同可執行狀態,如有,則把CPU的佔有權交給次執行緒,否則繼續執行原來的執行緒,所以yield()方法稱為退讓,它把執行機會讓給了同等級的其他執行緒

參考文獻