什麼是線程、併發-J.U.C併發系列(2)

回顧

回顧上一篇的文章,咱們主要介紹了現代計算機模型,CPU的緩存一致性協議,CPU和內存的工做原理,這些知識點都是爲了更好的去學習咱們的Java併發編程。java

介紹

本文,咱們來了解一個概念,什麼是線程?
Java中線程和計算機的線程有什麼區別?

什麼是線程

現代操做系統在運行一個程序時,會爲其建立一個進程。例如,啓動一個Java程序、網頁、軟件應用等,操做系統就會建立一個進程。現代操做系統調度CPU的最小單元是線程,也叫輕量級進程(Light Weight Process),在一個進程裏能夠建立多個線程,這些線程都擁有各自的堆棧、局部變量、計數器等屬性,而且可以訪問共享的內存變量。處理器在這些線程上高速切換,讓咱們感受到這些線程在同時併發執行。算法

進程是系統分配資源的基本單位,線程是調度CPU的基本單位,一個進程至少包含一個執行線程(main線程),線程依附在進程當中,每一個線程都有一組寄存器(保存當前線程的工做變量)、堆棧(記錄執行歷史,其中每一幀保存了一個已經調用但未返回的過程)、一個程序計數器(記錄要執行的下一條操做指令)編程

CPU會提供一個時間片來執行線程的代碼塊,而後快速的切換不一樣的線程,以達到併發的效果。緩存

須要知道,咱們的JVM中的Thread是沒法直接操做CPU的,JVM是依賴的底層的操做系統,所以會帶來一個概念,線程類型。安全

操做系統空間

  • 內核空間數據結構

    • 系統核心,底層的進程
  • 用戶空間多線程

    • JVM
    • eclipse應用
    • 視頻播放器

線程類型併發

  • 用戶級線程(User-Level Thread)
  • 內核線線程(Kernel-Level Thread)

線程級別類型

CPU級別eclipse

Intel的CPU將特權級別分爲4個級別:RING0,RING1,RING2,RING3。Windows只使用其中的兩個級別RING0和RING3,RING0只給操做系統用,RING3誰都能用。若是普通應用程序企圖執行RING0指令,則Windows會顯示「非法指令」錯誤信息。在用戶空間中,JVM會建立一個ULT級別線程,只能擁有Ring3級別權限。函數

而Ring0級別ULT是沒法去調用操做,至於爲何要這樣劃分?

出發是爲了安全性考慮,假如ULT能夠任意去操做CPU,擁有Ring0級別,那JVM的線程去肆意的攻擊,修改其餘的進程的指令,數據。會致使安全性問題。若是不限制,那內核裏面的指令能夠被修改,病毒能夠隨意的植入。

線程調度

JVM假如須要生成一個內核級線程的話,能夠怎麼操做?

能夠經過調用內核空間提供的系統調用接口(JNI)去建立一個KLT級別線程。

建立了KLT級別線程以後,才能夠去使用CPU,纔會被分配時間片。

用戶線程

指不須要內核支持而在用戶程序中實現的線程,其不依賴於操做系統核心,應用進程利用線程庫提供建立、同步、調度和管理線程的函數來控制用戶線程。另外,用戶線程是由應用進程利用線程庫建立和管理,不依賴於操做系統核心。不須要用戶態/核心態切換(上下文切換),速度快。操做系統內核不知道多線程的存在,所以一個線程阻塞將使得整個進程(包括它的全部線程)阻塞。因爲這裏的處理器時間片分配是以進程爲基本單位,因此每一個線程執行的時間相對減小。

內核線程

線程的全部管理操做都是由操做系統內核完成的。內核保存線程的狀態和上下文信息,當一個線程執行了引發阻塞的系統調用時,內核能夠調度該進程的其餘線程執行。在多處理器系統上,內核能夠分派屬於同一進程的多個線程在多個處理器上運行,提升進程執行的並行度。因爲須要內核完成線程的建立、調度和管理,因此和用戶級線程相比這些操做要慢得多,可是仍然比進程的建立和管理操做要快。大多數市場上的操做系統,如Windows,Linux等都支持內核級線程。

原理區分以下

咱們看KLT,每一個進程中的線程,所有依附於內核中,在內核中都會有對一個線程表一一對應,能夠理解爲輕量級的小進程,對應於具體的那個用戶空間的線程的具體任務,同時也擁有Ring0級別的CPU特權。

Java中建立的是哪一個級別的線程?

  • 1.2時,建立的是ULT
  • 1.2以後,建立的是KLT
private native void start0();

Java線程與系統內核線程關係

JVM建立線程以後,會去經過庫調度器調用,在內核空間中生成一個內核線程,並在內核空間的線程表關係中,進行一一映射對應。

Java建立線程

  1. new java.lang.Thread().start()
  2. 使用JNI將一個native thread attach到JVM中

針對 new java.lang.Thread().start()這種方式,只有調用start()方法的時候,纔會真正的在

JVM中去建立線程,主要的生命週期步驟有

  1. 建立對應的JavaThread的instance
  2. 建立對應的OSThread的instance
  3. 建立實際的底層操做系統的native thread
  4. 準備相應的JVM狀態,好比ThreadLocal存儲空間分配等
  5. 底層的native thread開始運行,調用java.lang.Thread生成的Object的run()方法
  6. 當java.lang.Thread生成的Object的run()方法執行完畢返回後,或者拋出異常終止後,終止native thread
  7. 釋放JVM相關的thread的資源,清除對應的JavaThread和OSThread

針對JNI將一個native thread attach到JVM中,主要的步驟有

  1. 經過JNI call AttachCurrentThread申請鏈接到執行的JVM實例
  2. JVM建立相應的JavaThread和OSThread對象
  3. 建立相應的java.lang.Thread的對象
  4. 一旦java.lang.Thread的Object建立以後,JNI就能夠調用Java代碼了
  5. 當經過JNI call DetachCurrentThread以後,JNI就從JVM實例中斷開鏈接
  6. JVM清除相應的JavaThread, OSThread, java.lang.Thread對象

Java線程的生命週期

以下圖所示

爲何用到併發?併發會產生什麼問題?

爲何用到併發

併發編程的本質其實就是利用多線程技術,在現代多核的CPU的背景下,催生了併發編程的趨勢,經過併發編程的形式能夠將多核CPU的計算能力發揮到極致,性能獲得提高。除此以外,面對複雜業務模型,並行程序會比串行程序更適應業務需求,而併發編程更能吻合這種業務拆分 。

即便是單核處理器也支持多線程執行代碼,CPU經過給每一個線程分配CPU時間片來實現這個機制。時間片是CPU分配給各個線程的時間,由於時間片很是短,因此CPU經過不停地切換線程執行,讓咱們感受多個線程是同時執行的,時間片通常是幾十毫秒(ms)。

併發不等於並行:併發指的是多個任務交替進行,而並行則是指真正意義上的「同時進行」。實際上,若是系統內只有一個CPU,而使用多線程時,那麼真實系統環境下不能並行,只能經過切換時間片的方式交替進行,而成爲併發執行任務。真正的並行也只能出如今擁有多個CPU的系統中。

併發的優勢

  1. 充分利用多核CPU的計算能力;
  2. 方便進行業務拆分,提高應用性能;

併發產生的問題

  • 高併發場景下,致使頻繁的上下文切換
  • 臨界區線程安全問題,容易出現死鎖的,產生死鎖就會形成系統功能不可用
  • 其它

CPU經過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。可是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,能夠再加載這個任務的狀態。因此任務從保存到再加載的過程就是一次上下文切換。

線程上下文切換過程:

上下文切換

image.png

Linux爲內核代碼和數據結構預留了幾個頁框,這些頁永遠不會被轉出到磁盤上。從0x00000000 到 0xc0000000(PAGE_OFFSET) 的線性地址可由用戶代碼 和 內核代碼進行引用(即用戶空間)。從0xc0000000(PAGE_OFFSET)到 0xFFFFFFFFF的線性地址只能由
內核代碼進行訪問(即內核空間)。內核代碼及其數據結構都必須位於這 1 GB的地址空間中,可是對於此地址空間而言,更大的消費者是物理地址的虛擬映射。

這意味着在 4 GB 的內存空間中,只有 3 GB 能夠用於用戶應用程序。一個進程只能運行在用戶方式(usermode)或內核方式(kernelmode)下。用戶程序運行在用戶方式下,而系統調用運行在內核方式下。在這兩種方式下所用的堆棧不同:用戶方式下用的是通常的堆棧,而內核方式下用的是固定大小的堆棧(通常爲一個內存頁的大小)。

每一個進程都有本身的 3 G 用戶空間,它們共享1GB的內核空間。當一個進程從用戶空間進入內核空間時,它就再也不有本身的進程空間了。這也就是爲何咱們常常說線程上下文切換會涉及到用戶態到內核態的切換緣由所在。

以上圖爲例,來介紹下,CPU的上下文切換

第一步

線程A申請到了時間片A,執行相關的業務邏輯,當時間到達以後,CPU紙箱執行線程B的時間片B

這個時候線程A須要把一個臨時中間狀態進行存儲,以便以後繼續執行。

會把執行的結果經過CPU寄存器 ---> 緩存 -- >經過bus總線(緩存一致性協議)寫回到主內存中。

中間的一些狀態會存放到主內存中的內核空間,一個叫作Tss任務狀態段的地方,存儲了程序指令、程序指針、中間數據等。

第二步

執行時間片B,執行完以後繼續指向線程A的時間片A。

這個時候CPU須要從新想內存中load上一個時間片執行的中間結果程序指令、程序指針、中間數據。

而後從新繼續執行線程A的邏輯。

小結

本文介紹了什麼是線程,併發,上下文切換的相關知識,但願對你有所幫助。

相關文章
相關標籤/搜索