Java內存模型與線程(二)線程的實現和線程的調度

先行先發生原則(happen-before原則)

先行先發生是指Java內存模型中定義的兩項操作之間的偏序關係。

如果說A先行於B,其實就是說在發生B操作之前,操作A產生的影響能被操作B觀察到,至於這個影響可以是修改內存中的共享變量也可以是發送消息、調用某個方法等。

happen-before要求前一個操作的執行結果對後一個操作可見,並且前一個操作按照順序排在第二個操作之前。

happen-before規則:

  1. **程序次序規則:**在一個線程內,按照程序代碼順序,書寫在前面的操作要先行發生於書寫在後面的操作。準確的說,應該是控制流順序而不是程序代碼的順序。
  2. 管程鎖定規則:一個unlock操作要先行發生於lock。這裏需要強調的是通一把鎖。
  3. volatile變量規則:對一個volatile變量的寫操作線性發生於後面對這個變量的讀操作,後面是指時間的先後順序。
  4. 線程啓動規則:Thread對象的start方法先行發生於此線程的每個動作。
  5. 線程終止規則:線程中的所有操作都先行發生於對此線程的終止檢測,比如線程A中執行ThreadB.join();那麼線程B中的任意操作先行於A從ThreadB.join()操作成功返回。
  6. 線程中斷規則:對線程Thread.interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生。
  7. 對象終結規則:一個對象的初始化完成要先行於他的finalize()方法的開始。
  8. 傳遞性:如果操作A先行發生於操作B,操作B先行發生於操作C,那就可以得出操作A先行發生於C。

時間先後順序與happen-before原則之間基本沒有太大的關係,所以我們衡量併發安全問題的時候不要受到時間順序的干擾,一切以happen-before原則爲準。

實現線程的三種方式

線程是比進程更輕量的調度執行單位,線程的引入是把進程的資源分配和調度分開,進程是資源分配的基本單位,線程以後成爲了執行調度的基本單位

實現線程的的方式主要有三種,分別是內核線程實現,用戶線程實現,用戶線程和輕量級進程組合實現

使用內核線程實現

內核線程是由操作系統內核支持的線程,內核通過調度器來調度內核線程,並負責將線程的任務映射到各個處理器上。每個內核線程可以視爲內核的一個分身。支持多線程的內核叫做多線程內核。

程序不會直接使用內核線程而是通過接口–輕量級進程來調用。每個輕量級進程都對應一個內核線程,所以他們之間的比例是1:1的關係。

在這裏插入圖片描述

使用輕量級進程實現線程的優點:因爲有內核線程的支持,每個輕量級進程都會成爲一個獨立的調度單位,即使其中有一個輕量級進程在系統調用過程中阻塞了,也不會影響整個進程繼續工作。

使用輕量級進程實現線程的缺點:輕量級進程需要很多次的系統調用,系統調用的代價是很高的,操作系統需要不停的在用戶態和 內核態之間進行切換。每個輕量級進程都有一個內核線程的支持,因此輕量級線程耗費的不少內核資源,所以說內核中的輕量級進程的數量是有限的。

使用用戶線程實現

從廣義上講,線程不是內核線程就是用戶線程,其實要這麼來說,輕量級進程也是用戶線程。

從狹義上講。用戶線程是指完全建立在用戶空間的線程庫上,系統內核不會感知到用戶線程的存在,其中用戶線程的同步,創建,銷燬都在用戶態下進行,不需要內核的參與。因此這種實現是非常快速並且是低消耗的。所以部分高性能的數據庫中的多線程就是由多用戶線程實現的。這種實現方式下的進程和線程之比是 1:N。也稱爲一對多線程模型。

在這裏插入圖片描述
這種實現方式的優點:不需要內核的幫助,快速高效且低消耗。
缺點:由於沒有內核參與所以考慮的問題是非常多的,導致實現過程過於複雜。

用戶線程和輕量級進程混合實現

線程除了以上兩種實現方式外,還有用戶線程和輕量級進程的混合使用。在這種模式下,既存在用戶線程也存在輕量級進程。操作系統提供輕量級進程作爲用戶線程和內核線程之間的橋樑。這樣可以使用內核提供的線程調度功能以及處理器映射,並且用戶線程的系統調用通過輕量級進程來完成,大大獎勵了整個進程被完全阻塞的風險,在這種模式下,用戶線程和輕量級進程的數量都是不確定的,所以是N:M 的關係。Unix系列的操作系統就提供了這種實現模式。
在這裏插入圖片描述

Java線程的實現

在JDK1.2之前是基於稱爲“綠色線程”的用戶線程來實現的。在JDK1.2中,線程模型改爲基於操作系統的線程模型,換句話說,操作系統選擇哪種線程模型,Java虛擬機就選擇哪種線程映射方式。線程模型只對線程的併發規模和操作成本產生影響,對Java的運行過程來說這些差異是感受不到的。

在Windows和Linux操作系統上,兩個操作系統都選擇的是1:1線程模型,一條Java線程就映射到一條輕量級進程中,因爲Windows和Linux系統提供的線程模型就是一對一的。

Java線程的調度

線程調度就是系統爲線程分配處理器使用權的過程,主要分爲兩種,一種是協同式,一種是搶佔式調度

如果使用系協同式調度,那就是線程把自己的任務執行完成後才切換到另一個線程,線程佔用處理器的時間由線程自身決定。協同式的好處就是實現簡單,切換線程對線程本身來講是可知的,所以沒有什麼線程同步問題。
它的缺點也很明顯,線程執行時間不受限制,如果一個線程編寫的有問題,那他將一直佔據處理器的使用,程序一直阻塞在那兒,相當不穩定。

搶佔式調度中的每個線程的執行時間交由系統來決定,線程之間的切換不是由線程自身決定。在這種實現線程調度的方式下,線程的執行時間是系統可控的,也不會有一個線程阻塞導致整個進程阻塞的問題,而Java就是採用的搶佔式線程調度。

我們可以“建議”系統給某些線程分配多一點執行時間,給一些線程分配少一些執行時間,整個可以通過設置線程優先級來實現。Java一共設置的有10個線程優先級,在兩個線程同時處於就緒態的時候,優先級越高的線程越容易被系統選中執行。不過有時候這種採用優先級的方式又不太靠譜,因爲Java的線程最後是映射到系統的原聲線程來實現的,所以線程調度最終取決於操作系統。如果操作系統中的優先級設置的種類比Java設置的優先級的種類少,那麼就會出現幾個優先級相同的情況了。例如Windows設置的線程優先級就只有7種,Java設置的有10種,所以Windows至少得重複3種纔可以和Java進行對應。

Java線程狀態之間的轉換

具體看我這篇博文

Java線程的狀態以及之間的轉換

參考:《深入理解Java虛擬機 第二版》周志明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章