《深入理解Java虛擬機》(三)--Java內存模型與線程(3)

Java與線程

併發並不一定要依賴多線程(比如PHP中很常見的多進程併發),但是Java裏面談論到併發,大多與線程脫不開關係。

1/1 線程的實現

主流操作系統都提供了線程實現,Java語言則提供了在不同硬件和操作系統平臺下對線程操作的統一處理,每個已經執行start()且還未結束的java.lang.Thread類的實例就代表了一個線程,Thread類與大部分API有明顯的差異,它的所有關鍵方法都是聲明Native的,一個Native方法意味着這個方法是使用非Java語言來實現的。線程實現的主要方式有三種:使用內核線程實現,使用用戶線程實現和使用用戶線程加輕量級進程混合實現。

  • 1.使用內核線程實現:內核線程就是直接由操作系統內核(Kernel)支持的線程,這種線程由內核來完成線程切換,內核通過調度器(Thread Scheduler)對線程進行調度,程序一般不會直接取使用內核線程,而是取使用內核線程的一種高級接口-----輕量級進程(LWP),輕量級進程就是我們通常意義上所說的線程,這種輕量級進程與內核之間1:1的關係成爲1對1線程模型,如下圖所示

    12350543-65e43d5e1355b9d1.png
    輕量級進程與內核線程之間1:1的關係

    優點:由於內核程序的支持,每個輕量級進程都成爲了一個獨立的調度單元,即使有一個輕量級進程發生了阻塞,也不會影響整個進程繼續工作。
    缺點:由於是基於內核線程實現的,所以各種線程操作(如創建,析構和同步)都需要進行系統調用,調用的代價相對較高,需要在用戶狀態和內核狀態來回切換,而且每個輕量級線程都需要內核進程的支持,因此一個系統支持輕量進程的個數是有限的。

  • 2.使用用戶線程實現
    用戶線程是指完全建立在用戶空間的線程庫上,系統內核不能感知線程存在。用戶線程的建立、同步、銷燬和調度完全在用戶態中完成,不需要內核的幫助。這種進程與用戶線程之間1:N的關係稱爲1對多線程模型,如下圖所示。

    12350543-f8d5c3cfdfbc6930.png
    進程與用戶線程之間1:N的關係

    優點:這種線程不需要切換到內核態,因此操作可以是非常快速而且低消耗的,也可以支持更大的線程數量,部分高性能數據庫的多線程就是由用戶線程實現。
    缺點:由於沒有系統內核的支援,所有線程操作都是由用戶程序自己處理,處理起來過於複雜,Java已經放棄使用它。

  • 3.使用用戶線程加輕量級進程混合實現
    除了上述兩種實現外,還有一種將內核線程與用戶線程一起使用的實現方式。這種混合方式下,既存在用戶線程也存在輕量級進程,用戶線程與輕量級線程的數量比是不確定的,即爲N:M的關係,如下圖所示,這種就是多對多線程模式(讓我想起了Mysql數據庫的關係模式....)。

    12350543-fde3f3c4739dc691.png
    用戶線程與輕量級線程之間N:M的關係

    優點:用戶線程完全建立在用戶空間中,因此用戶線程的創建、析構、切換等操作依然廉價,並且可以支持大規模的用戶併發,書中並沒有詳細寫出它的缺點0.0。

java線程的實現

在JDK1.2之後,線程模式爲基於操作系統原生的線程模型來實現,線程模型只對併發規模和操作成本產生影響,對Java程序的編碼和運行來說沒有影響。Windows和Linux提供的線程模型爲1對1,Solaris(Unix系列的操作系統)同時支持1對1和多對多,因此Solaris版的JDK中也對應提供了兩個平臺專有的虛擬機參數:-XX:+UseLWPSynchronization(默認值)和-XX:UseBoundThreads來明確指定虛擬機使用哪種線程模式。

1/2 Java線程調度

線程調度是指系統爲線程分配處理器使用權的過程。主要調度方法有兩種,分別是協同式線程調度,和搶佔式線程調度。

  • 協同式線程調度:線程的執行時間由線程本身來控制,執行完成後,主動通知系統切換到另外一個線程上。
    優點:實現簡單,切換操作對線程自己來說是可知的,所以不會產生線程同步的問題。
    缺點:線程執行時間不可控制,可能由於程序編寫問題一直不告知系統進行線程切換,那麼程序就會一直阻塞在那裏,相當不穩定。
  • 搶佔式線程調度:每個線程將由系統來分配時間,線程的切換不由線程本身來控制。
    優點:不會因爲一個線程而導致整個系統的阻塞,Java使用的線程調度方式就是搶佔式調度(JDK後續版本可能會提供協成方式來進行多任務處理)。
    缺點:需要處理線程安全的問題。

1/3 狀態轉換

Java語言定義了5種線程狀態,在任意一個時間點,一個線程只能有且只有一種狀態。這5種狀態如下:

  • 新建(New):創建後尚未啓動的線程處於這種狀態。
  • 運行(Runnble):包括了操作系統線程狀態中的Running和Ready,也就是處於此狀態的線程有可能正在執行,也可能在等待CPU爲他分配執行時間。
  • 無限期等待(Waiting):處於這種狀態的線程不會被分配CPU執行時間,他們要等待被其他線程喚醒,以下方法可以讓線程進入無限期等待。
    • 沒有設置Timeout參數的Object.wait()方法。
    • 沒有設置Timeout參數的Thread.join()方法。
    • LockSuppot.park()方法。
  • 限期等待(Timed Waiting):處於這種狀態的線程也不會被CPU分配執行時間,不過無需等待被其他線程喚醒,在一定時間後他們會由系統自動喚醒,以下方法會讓線程進入限期等待狀態。
    • Thread.sleep()方法。
    • 設置了Timeout參數的Object.wait()方法。
    • 設置了Timeout參數的Thread.join()方法。
    • LockSuppot.parkNanos()方法。
    • LockSuppot.parkUntil()方法。
  • 阻塞(Blocked):線程被阻塞了,阻塞狀態等待着獲取到一個排他鎖,這個時間將在另外一個線程放棄這個鎖的時候發生;在程序等待進入同步區域的時候,線程將進入這種狀態。
  • 結束(Terminated):已終止線程的線程狀態,線程已經結束執行。

上述5種狀態在遇到特定事件的時候可以相互轉換,它們的轉換關係如下圖所示。


12350543-b4fe770d46012af1.png
線程狀態轉換關係
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章