日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

深入理解Java虚拟机(周志明第三版)- 第十二章:Java内存模型与线程

發(fā)布時(shí)間:2023/12/8 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解Java虚拟机(周志明第三版)- 第十二章:Java内存模型与线程 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

系列文章目錄

第一章: 走近Java
第二章: Java內(nèi)存區(qū)域與內(nèi)存溢出異常
第三章: Java垃圾收集器與內(nèi)存分配策略


并發(fā)處理的廣泛應(yīng)用是Amdahl定律代替摩爾定律成為計(jì)算機(jī)性能發(fā)展源動(dòng)力的根本原因,也是人類(lèi)壓榨計(jì)算機(jī)運(yùn)算能力的最有力武器

  • 系列文章目錄
  • 一、概述
  • 二、硬件的效率和一致性
  • 三、Java內(nèi)存模型
    • 1、主內(nèi)存與工作內(nèi)存
    • 2、內(nèi)存間交互
    • 3、對(duì)于volatile型變量的特殊規(guī)則
    • 4、針對(duì)long和double變量的特殊規(guī)則
    • 5、原子性、可見(jiàn)性與有序性
      • 原子性
      • 可見(jiàn)性
      • 有序性
    • 6、先行發(fā)生規(guī)則
  • 四、Java與線程
    • 1、線程的實(shí)現(xiàn)
      • 使用內(nèi)核線程實(shí)現(xiàn)(1:1實(shí)現(xiàn))
      • 使用用戶線程實(shí)現(xiàn)(1:N實(shí)現(xiàn))
      • 使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)(N:M實(shí)現(xiàn))
      • Java線程如何實(shí)現(xiàn)
    • 2、Java線程調(diào)度
    • 3、狀態(tài)轉(zhuǎn)換
  • 五、Java與協(xié)程(了解即可)
    • 1、內(nèi)核線程的局限
    • 2、協(xié)程的復(fù)蘇
    • 3、Java的解決方案
  • 六、附錄

一、概述

????????多任務(wù)處理是現(xiàn)代計(jì)算機(jī)幾乎必備的功能,不僅是因?yàn)橛?jì)算機(jī)的運(yùn)算能力強(qiáng)大了,還有一個(gè)很重要的原因是計(jì)算機(jī)的運(yùn)算速度與它的存儲(chǔ)和通信子系統(tǒng)的速度差距太大,大量的時(shí)間花費(fèi)在磁盤(pán)IO、網(wǎng)絡(luò)通信或數(shù)據(jù)庫(kù)訪問(wèn)上,為了不希望處理器在大部分時(shí)間里都處于等待其他資源的空閑狀態(tài),就必須使用一些手段壓榨處理器的運(yùn)算能力,否則會(huì)造成很大的性能浪費(fèi),而讓計(jì)算機(jī)同時(shí)處理幾項(xiàng)任務(wù)則是最容易想到的,也被證明是非常有效的壓榨手段。

????????另外一種更具體的并發(fā)應(yīng)用場(chǎng)景就是一個(gè)服務(wù)端同時(shí)對(duì)多個(gè)客戶端提供服務(wù)。衡量一個(gè)服務(wù)性能的好壞,每秒事務(wù)處理數(shù)(TPS)是重要的指標(biāo)之一,它代表著一秒內(nèi)服務(wù)端平均能響應(yīng)的請(qǐng)求總數(shù),而TPS與程序的并發(fā)能力有非常密切的關(guān)系。對(duì)于計(jì)算量相同的任務(wù),線程并發(fā)協(xié)調(diào)越有條不紊,效率越高;線程間競(jìng)爭(zhēng)頻繁,互相阻塞甚至死鎖,將會(huì)大大降低程序并發(fā)能力。

????????服務(wù)端應(yīng)用是Java語(yǔ)言擅長(zhǎng)的領(lǐng)域之一。而Java語(yǔ)言和虛擬機(jī)提供了許多工具,將并發(fā)編程的門(mén)檻降低了不少,且各種中間件、各類(lèi)框架等也可能隱藏線程并發(fā)細(xì)節(jié),使得程序員在編碼時(shí)更關(guān)注業(yè)務(wù)邏輯。

但是無(wú)論語(yǔ)言、中間件和框架再如何先進(jìn),開(kāi)發(fā)人員都不應(yīng)期望它們能獨(dú)立完成所有并發(fā)處理的事情,了解并發(fā)的內(nèi)幕仍然是成為一個(gè)高級(jí)程序員不可缺少的課程。


二、硬件的效率和一致性

什么是高速緩存?為什么需要高速緩存
????????物理機(jī)絕大多數(shù)的計(jì)算任務(wù)都不可能只靠處理器計(jì)算完成,處理器至少要與內(nèi)存交互,如讀取運(yùn)算數(shù)據(jù)、存儲(chǔ)運(yùn)算結(jié)果等,無(wú)法僅僅依靠寄存器完成所運(yùn)算任務(wù)。由于計(jì)算機(jī)的存儲(chǔ)設(shè)備與處理器的運(yùn)算速度有幾個(gè)數(shù)量級(jí)的差距,所以現(xiàn)代計(jì)算機(jī)不得不加入一層或多層讀寫(xiě)速度盡可能接近處理器運(yùn)算速度的高速緩存來(lái)作為內(nèi)存與處理器之間的緩沖:將運(yùn)算需要使用的數(shù)據(jù)復(fù)制到緩存中,讓運(yùn)算能快速進(jìn)行,當(dāng)運(yùn)算結(jié)束后再?gòu)木彺嫱交貎?nèi)存中,這樣處理器就無(wú)須等待緩慢的內(nèi)存讀寫(xiě)了。

什么是緩存一致性問(wèn)題?如何解決?
在多路處理器系統(tǒng)中,每個(gè)處理器都有自己的高速緩存,而它們又共享同一主內(nèi)存,這種系統(tǒng)成為共享多核系統(tǒng),當(dāng)多個(gè)處理器的運(yùn)算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時(shí),將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致,如果發(fā)生這種情況,那同步回到主內(nèi)存時(shí)該以誰(shuí)的緩存數(shù)據(jù)為準(zhǔn)呢?為了解決一致性問(wèn)題,需要各個(gè)處理器訪問(wèn)緩存時(shí)遵循一些協(xié)議,在讀寫(xiě)時(shí)要根據(jù)協(xié)議來(lái)進(jìn)行操作,這類(lèi)協(xié)議有MSI、MESI等。

內(nèi)存模型可以理解為在特定的操作協(xié)議下,對(duì)特定的內(nèi)存或高速緩存進(jìn)行讀寫(xiě)訪問(wèn)的過(guò)程抽象。不同架構(gòu)的物理機(jī)器可以擁有不一樣的內(nèi)存模型。

除了增加高速緩存之外,為了使處理器內(nèi)部的運(yùn)算單元能盡量被充分使用,處理器可能會(huì)對(duì)輸入代碼進(jìn)行亂序執(zhí)行優(yōu)化,處理器會(huì)在計(jì)算之后將亂序執(zhí)行的結(jié)果重組,保證該結(jié)果與順序執(zhí)行的結(jié)果是一致的,但并不保證程序中各個(gè)語(yǔ)句計(jì)算的先后順序與輸入代碼中的順序一致,因此如果存在一個(gè)計(jì)算任務(wù)依賴另外一個(gè)計(jì)算任務(wù)的中間結(jié)果,那么其順序性并不能靠代碼的先后順序來(lái)保證。與處理器的亂序執(zhí)行優(yōu)化類(lèi)似,Java虛擬機(jī)的即時(shí)編譯器也有指令重排序優(yōu)化。


三、Java內(nèi)存模型

Java內(nèi)存模型屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果。

1、主內(nèi)存與工作內(nèi)存

????????Java內(nèi)存模型的主要目的是定義了程序中各種變量的訪問(wèn)規(guī)則,即關(guān)注在虛擬機(jī)中把變量值存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量值這樣的底層操作細(xì)節(jié)。這里的變量包括了實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但是不包括局部變量與方法參數(shù),因?yàn)楹笳呤蔷€程私有的,不會(huì)被共享,自然就不存在競(jìng)爭(zhēng)問(wèn)題,且為了獲得更好的執(zhí)行效能,Java內(nèi)存模型并沒(méi)有限制執(zhí)行引擎使用處理器的特定寄存器或緩存來(lái)和主內(nèi)存進(jìn)行交互,也沒(méi)有限制即時(shí)編譯器是否需要進(jìn)行調(diào)整代碼執(zhí)行順序這類(lèi)優(yōu)化措施。

Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每個(gè)線程還有自己的工作內(nèi)存(類(lèi)比高速緩存),線程的工作內(nèi)存中保存了該線程使用的變量的主內(nèi)存副本,線程對(duì)變量的所有讀寫(xiě)操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存中的數(shù)據(jù),不同線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞都需要通過(guò)主內(nèi)存來(lái)完成。如圖:

2、內(nèi)存間交互

????????關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存這一類(lèi)的實(shí)現(xiàn)細(xì)節(jié)。Java內(nèi)存模型定義了以下8種操作來(lái)完成,Java虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證下面提及的每一種操作都是原子的、不可再分的:

  • lock(鎖定):作用于主內(nèi)存的變量,將一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)
  • unlock(解鎖):作用于主內(nèi)存的變量,將一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定
  • read(讀取):作用于主內(nèi)存的變量,將一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load使用
  • load(載入):作用于工作內(nèi)存的變量,將read操作讀取的變量值放入工作內(nèi)存的變量副本中
  • use(使用):作用于工作內(nèi)存的變量,將工作內(nèi)存中變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到需要使用變量值的字節(jié)碼指令時(shí)將執(zhí)行該操作
  • assign(賦值):作用于工作內(nèi)存的變量,將從執(zhí)行引擎接收的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到給變量賦值的字節(jié)碼指令時(shí)執(zhí)行該操作
  • store(存儲(chǔ)):作用于工作內(nèi)存的變量,將工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write使用
  • write(寫(xiě)入):作用于主內(nèi)存的變量,將store操作從工作內(nèi)存取到的變量值放入主內(nèi)存的變量中

如果將一個(gè)變量從主內(nèi)存拷貝到工作內(nèi)存中,就要按順序執(zhí)行read和load操作,如果把變量從工作內(nèi)存同步回主內(nèi)存,就要按順序執(zhí)行store和write操作。Java內(nèi)存模型只要求上述兩個(gè)操作必須順序執(zhí)行,但不要求是連續(xù)執(zhí)行,即可能:read:a read:b load:b load:a。除此之外,Java內(nèi)存模型還規(guī)定了在執(zhí)行上述8種基本操作時(shí)必須滿足如下規(guī)則:

  • 不允許read和load、store和write單一出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接收,或工作內(nèi)存發(fā)起回寫(xiě)但主內(nèi)存不接收的情況
  • 不允許一個(gè)線程丟失它最近的assign操作,即變量在工作內(nèi)存中改變后必須把變化同步回主內(nèi)存
  • 不允許一個(gè)線程無(wú)原因地把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中
  • 一個(gè)新的變量只能在主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個(gè)未經(jīng)初始化的變量,即對(duì)一個(gè)變量use、store操作前,必須先執(zhí)行assign和load操作
  • 一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)解鎖
  • 如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,那將會(huì)清空工作內(nèi)存中此變量的值,執(zhí)行引擎使用這個(gè)變量值前,需要重新執(zhí)行l(wèi)oad或assign操作以初始化變量的值
  • 如果一個(gè)變量事先沒(méi)有被lock操作鎖定,那就不允許對(duì)它執(zhí)行unlock操作,也不允許unlock一個(gè)被其他線程鎖定的變量
  • 對(duì)一個(gè)變量執(zhí)行unlock操作前,必須先把此變量同步回主內(nèi)存中,

3、對(duì)于volatile型變量的特殊規(guī)則

關(guān)鍵字volatile是虛擬機(jī)提供的最輕量級(jí)的同步機(jī)制。

變量修飾為volatile有什么用?
1、保證此變量對(duì)所有線程的可見(jiàn)性,即當(dāng)一條線程修改了這個(gè)變量的值,新值對(duì)于其他線程來(lái)說(shuō)是可以立即得知的,而普通變量的值在線程間傳遞時(shí)均需要通過(guò)主內(nèi)存來(lái)完成,例線程A修改一個(gè)普通變量的值后,然后向主內(nèi)存進(jìn)行回寫(xiě),另外一條線程B在線程A回寫(xiě)完成后再對(duì)主內(nèi)存進(jìn)行讀取操作,新變量值才對(duì)線程B可見(jiàn)。

volatile變量在各個(gè)線程的工作內(nèi)存中是不存在一致性問(wèn)題的(物理存儲(chǔ)角度看,各個(gè)線程的工作內(nèi)存中volatile變量也可以存在不一致的情況,但由于每次使用之前都要先刷新,執(zhí)行引擎看不到不一致的情況,因此可以認(rèn)為不存在一致性問(wèn)題,但是Java里運(yùn)算操作符并非原子操作(自增++、自減–等),這導(dǎo)致volatile變量的運(yùn)算在并發(fā)下一樣是不安全的)
????????
由于volatile變量只能保證可見(jiàn)性,在不符合以下兩條規(guī)則的運(yùn)算場(chǎng)景下,仍然要通過(guò)加鎖(synchronized、current并發(fā)包中的鎖或原子類(lèi))來(lái)保證原子性:
????????-運(yùn)算結(jié)果不依賴變量的當(dāng)前值,或者確保只有單一的線程修改變量值
????????-變量不需要與其他的狀態(tài)變量共同參與不變約束

2、禁止指令重排序優(yōu)化,普通的變量?jī)H會(huì)保證在該方法的執(zhí)行過(guò)程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致

4、針對(duì)long和double變量的特殊規(guī)則

什么是long和double的非原子性協(xié)定?
????????Java內(nèi)存模型要求lock、unlock、read、load、assign、use、store、write這8種操作都具有原子性,但是對(duì)于64位的數(shù)據(jù)類(lèi)型,在模型中定義了一條寬松的規(guī)定:允許虛擬機(jī)將沒(méi)有被volatile修飾的64位數(shù)據(jù)類(lèi)型的讀寫(xiě)操作劃分為兩次32位的操作來(lái)進(jìn)行,即允許虛擬機(jī)實(shí)現(xiàn)自行選擇是否要保證64位數(shù)據(jù)類(lèi)型的load、store、read、write這4個(gè)操作的原子性。

5、原子性、可見(jiàn)性與有序性

原子性

由Java內(nèi)存模型直接保證的原子性變量操作包括read、load、assign、use、store和write這6個(gè),大致可以認(rèn)為,基本數(shù)據(jù)類(lèi)型的訪問(wèn)、讀寫(xiě)都是原子性的,此外更大范圍的原子性保證,Java內(nèi)存模型還提供了lock和unlock操作來(lái)滿足需求,如更高層次的字節(jié)碼指令monitorenter和monitorexit來(lái)隱式地使用,這兩個(gè)字節(jié)碼指令反映到Java代碼中就是同步塊-synchronized關(guān)鍵字

可見(jiàn)性

指當(dāng)一個(gè)線程修改了變量值后,其他線程能夠立即得知這個(gè)修改。volatile變量的特殊規(guī)則保證了新值能立即同步到主內(nèi)存中,以及每次使用前立即從主內(nèi)存刷新。

有序性

在本線程內(nèi)觀察,所有的操作都是有序的(線程內(nèi)似表現(xiàn)為串行的語(yǔ)義);如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無(wú)序的(指令重排序現(xiàn)象和工作內(nèi)存與主內(nèi)存同步延遲現(xiàn)象)。

6、先行發(fā)生規(guī)則

為什么有這個(gè)規(guī)則?
如果Java內(nèi)存模型中所有的有序性都依靠volatile和synchronized來(lái)完成,那么將會(huì)有很多操作變得非常啰嗦,但是我們?cè)诰帉?xiě)Java程序時(shí)并沒(méi)有察覺(jué)到這一點(diǎn),就是因?yàn)镴ava語(yǔ)言中有一個(gè)先行發(fā)生原則

什么是先行發(fā)生?
先行發(fā)生是Java內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系。例如操作A先行發(fā)生于操作B,其實(shí)就是說(shuō)在發(fā)生操作B之前,操作A產(chǎn)生的影響能夠被操作B觀察到,"影響"包括了修改內(nèi)存中共享變量的值、發(fā)送消息、調(diào)用了方法等。

什么是Java內(nèi)存模型的先行發(fā)生規(guī)則?

  • 程序次序規(guī)則:在一個(gè)線程內(nèi),按照控制流順序,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作
  • 管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作
  • volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作
  • 線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每個(gè)動(dòng)作
  • 線程終止規(guī)則:線程中所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè)
  • 線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷的代碼檢測(cè)到中斷事件的發(fā)生
  • 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化操作先行發(fā)生于它的finalize()方法的開(kāi)始
  • 傳遞性:如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C,那么可以得出操作A先行發(fā)生于操作C

Java內(nèi)存模型的先行發(fā)生規(guī)則有什么用?
滿足上述先行發(fā)生規(guī)則的場(chǎng)景無(wú)需使用任何同步手段保障。


四、Java與線程

1、線程的實(shí)現(xiàn)

線程是比進(jìn)程更輕量級(jí)的調(diào)度執(zhí)行單元,線程的引入可以把一個(gè)進(jìn)程的資源分配和執(zhí)行調(diào)度分開(kāi),各個(gè)線程既可以共享進(jìn)程資源(內(nèi)存地址、文件IO等),又可以獨(dú)立調(diào)度。

實(shí)現(xiàn)線程主要有三種方式:

使用內(nèi)核線程實(shí)現(xiàn)(1:1實(shí)現(xiàn))

內(nèi)核線程指直接由操作系統(tǒng)內(nèi)核支持的線程,由內(nèi)核來(lái)完成線程切換,內(nèi)核通過(guò)操縱調(diào)度器對(duì)線程進(jìn)行調(diào)度,并負(fù)責(zé)將線程的任務(wù)映射到各個(gè)處理器上。

輕量級(jí)進(jìn)程是內(nèi)核線程的一種高級(jí)接口,也是我們通常意義講的線程,每一個(gè)輕量級(jí)進(jìn)程都由一個(gè)內(nèi)核線程支持。

優(yōu)劣:
每個(gè)輕量級(jí)進(jìn)程都成為一個(gè)獨(dú)立的調(diào)度單元,即使其中一個(gè)輕量級(jí)進(jìn)程阻塞了,也不會(huì)影響整個(gè)進(jìn)程繼續(xù)工作。但由于是基于內(nèi)核線程實(shí)現(xiàn)的,各種線程操作需要進(jìn)行系統(tǒng)調(diào)用,代價(jià)相對(duì)較高,需要在用戶態(tài)和內(nèi)核態(tài)來(lái)回切換,且一個(gè)輕量級(jí)進(jìn)程對(duì)應(yīng)一個(gè)內(nèi)核線程,輕量級(jí)進(jìn)程要消耗一定的內(nèi)核資源(內(nèi)核線程的棧空間),因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程的數(shù)據(jù)是有限的

使用用戶線程實(shí)現(xiàn)(1:N實(shí)現(xiàn))


用戶線程指完全建立在用戶空間的線程庫(kù)上,系統(tǒng)內(nèi)核不能感知到用戶線程的存在及如何實(shí)現(xiàn)的。用戶線程的創(chuàng)建、同步、銷(xiāo)毀、調(diào)度完全在用戶態(tài)中完成,不需要內(nèi)核的幫助。如果程序?qū)崿F(xiàn)得當(dāng),這種線程不需要切換到內(nèi)核態(tài),因此操作可以是非常快速且低消耗的,也能夠支持更大的線程數(shù)量。

優(yōu)劣:
用戶線程的優(yōu)勢(shì)在于不需要系統(tǒng)內(nèi)核支援,劣勢(shì)也在于沒(méi)有系統(tǒng)內(nèi)核支援,所有的線程操作都需要用戶程序自己去處理,因此用戶線程實(shí)現(xiàn)的程序通常都比較復(fù)雜。

使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)(N:M實(shí)現(xiàn))


混合實(shí)現(xiàn)下,既存在用戶線程,也存在輕量級(jí)進(jìn)程。用戶線程還是完全建立在用戶空間中,因此用戶線程的各種操作依然廉價(jià),且可以支持大規(guī)模的用戶并發(fā)。而操作系統(tǒng)支持的輕量級(jí)進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁,這樣做可以使用內(nèi)核提供的線程調(diào)度功能及處理器映射,并且用戶線程的系統(tǒng)調(diào)用要通過(guò)輕量級(jí)進(jìn)程來(lái)完成,大大降低了整個(gè)進(jìn)程被完全阻塞的風(fēng)險(xiǎn)。

Java線程如何實(shí)現(xiàn)

Java線程的實(shí)現(xiàn)并不受Java虛擬機(jī)規(guī)范的約束,這是一個(gè)與具體虛擬機(jī)相關(guān)的話題。
目前主流平臺(tái)上的主流商用虛擬機(jī)的線程模型普遍使用基于操作系統(tǒng)原生線程模型實(shí)現(xiàn),即1:1實(shí)現(xiàn)模型

Hotspot虛擬機(jī)中,它的每一個(gè)Java線程都是直接映射到一個(gè)操作系統(tǒng)原生線程來(lái)實(shí)現(xiàn)的,而且中間沒(méi)有額外的間接結(jié)構(gòu),所以Hotspot是不會(huì)干涉線程調(diào)度的,全權(quán)交給底層的操作系統(tǒng)去處理。

2、Java線程調(diào)度

線程調(diào)度指系統(tǒng)為線程分配處理器使用權(quán)的過(guò)程,調(diào)度主要方式有兩種:協(xié)同式線程調(diào)度和搶占式線程調(diào)度。

協(xié)同式線程調(diào)度:
指線程的執(zhí)行時(shí)間由線程本身控制,線程把自己的工作執(zhí)行完后,要主動(dòng)通知系統(tǒng)切換到另外一個(gè)線程上去。協(xié)同式調(diào)度的好處是實(shí)現(xiàn)簡(jiǎn)單且切換動(dòng)作對(duì)自己本身是可知的,一般沒(méi)有什么線程同步的問(wèn)題;壞處是線程執(zhí)行時(shí)間不可控,可能會(huì)一個(gè)線程阻塞影響整個(gè)進(jìn)程或系統(tǒng)

搶占式線程調(diào)度:
指每個(gè)線程的執(zhí)行時(shí)間由系統(tǒng)來(lái)分配,線程的切換不由線程本身決定。在這種實(shí)現(xiàn)線程調(diào)度方式下,線程的執(zhí)行時(shí)間是系統(tǒng)可控的,也不會(huì)因?yàn)橐粋€(gè)線程導(dǎo)致整個(gè)進(jìn)程或系統(tǒng)阻塞

Java使用的線程調(diào)度方式是搶占式線程調(diào)度。Java語(yǔ)言一共設(shè)置了10個(gè)級(jí)別的線程優(yōu)先級(jí)。當(dāng)兩個(gè)線程同時(shí)處于Ready狀態(tài)時(shí),優(yōu)先級(jí)越高的線程越容易被系統(tǒng)選擇執(zhí)行(線程優(yōu)先級(jí)并不是一項(xiàng)穩(wěn)定的調(diào)節(jié)手段)。

3、狀態(tài)轉(zhuǎn)換

Java語(yǔ)言定義了6種線程狀態(tài),在任意一個(gè)時(shí)間點(diǎn)中,一個(gè)線程只能有且只有其中的一個(gè)狀態(tài),并且可以通過(guò)特定的方法在不同狀態(tài)之間轉(zhuǎn)換:

  • 新建(New):創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài)
  • 運(yùn)行(Runnable):包括操作系統(tǒng)線程狀態(tài)中的Running和Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著操作系統(tǒng)為它分配執(zhí)行時(shí)間
  • 無(wú)限期等待(Waiting):處于這種狀態(tài)的線程不會(huì)被分配處理器執(zhí)行時(shí)間,它們要等待被其他線程顯式喚醒。以下方法會(huì)讓線程陷入無(wú)限期的等待狀態(tài):沒(méi)有設(shè)置Timeout參數(shù)的Object::wait()方法;沒(méi)有設(shè)置Timeout參數(shù)的Thread::join()方法;LockSupport::park()方法。
  • 限期等待(Timed Waiting):處于這種狀態(tài)的線程也不會(huì)被分配處理器執(zhí)行時(shí)間,不過(guò)無(wú)須等待被其他線程顯式喚醒,在一定時(shí)間之后它們會(huì)由系統(tǒng)自動(dòng)喚醒。以下方法會(huì)讓線程進(jìn)入限期等待狀 態(tài):Thread::sleep()方法;設(shè)置了Timeout參數(shù)的Object::wait()方法;設(shè)置了Timeout參數(shù)的Thread::join()方法;LockSupport::parkNanos()方法;LockSupport::parkUntil()方法
  • 阻塞(Blocked):線程被阻塞了,“阻塞狀態(tài)”與“等待狀態(tài)”的區(qū)別是“阻塞狀態(tài)”在等待著獲取到一個(gè)排它鎖,這個(gè)事件將在另外一個(gè)線程放棄這個(gè)鎖的時(shí)候發(fā)生;而“等待狀態(tài)”則是在等待一段時(shí) 間,或者喚醒動(dòng)作的發(fā)生。在程序等待進(jìn)入同步區(qū)域的時(shí)候,線程將進(jìn)入這種狀態(tài)
  • 結(jié)束(Terminated):已終止線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行


五、Java與協(xié)程(了解即可)

1、內(nèi)核線程的局限

目前Java線程面臨的困境:對(duì)Web應(yīng)用的服務(wù)要求,不論是在請(qǐng)求數(shù)量上還是在復(fù)雜度上,與十多年前相比已不可同日而語(yǔ),這一方面是源于業(yè)務(wù)量的增長(zhǎng),另一方面來(lái)自于為了應(yīng)對(duì)業(yè)務(wù)復(fù)雜化而不斷進(jìn)行的服務(wù)細(xì)分。現(xiàn)代B/S系統(tǒng)中一次對(duì)外部業(yè)務(wù)請(qǐng)求的響 應(yīng),往往需要分布在不同機(jī)器上的大量服務(wù)共同協(xié)作來(lái)實(shí)現(xiàn),這種服務(wù)細(xì)分的架構(gòu)在減少單個(gè)服務(wù)復(fù)雜度、增加復(fù)用性的同時(shí),也不可避免地增加了服務(wù)的數(shù)量,縮短了留給每個(gè)服務(wù)的響應(yīng)時(shí)間。這要求每一個(gè)服務(wù)都必須在極短的時(shí)間內(nèi)完成計(jì)算,這樣組合多個(gè)服務(wù)的總耗時(shí)才不會(huì)太長(zhǎng);也要求每一個(gè)服務(wù)提供者都要能同時(shí)處理數(shù)量更龐大的請(qǐng)求,這樣才不會(huì)出現(xiàn)請(qǐng)求由于某個(gè)服務(wù)被阻塞而出現(xiàn)等待。

Java目前的并發(fā)編程機(jī)制就與上述架構(gòu)趨勢(shì)產(chǎn)生了一些矛盾,1:1的內(nèi)核線程模型是如今Java虛擬機(jī)線程實(shí)現(xiàn)的主流選擇,但是這種映射到操作系統(tǒng)上的線程天然的缺陷是切換、調(diào)度成本高昂,系統(tǒng)能容納的線程數(shù)量也很有限。以前處理一個(gè)請(qǐng)求可以允許花費(fèi)很長(zhǎng)時(shí)間在單體應(yīng)用中,具有這種線程切換的成本也是無(wú)傷大雅的,但現(xiàn)在在每個(gè)請(qǐng)求本身的執(zhí)行時(shí)間變得很短、數(shù)量變得很多的前提下, 用戶線程切換的開(kāi)銷(xiāo)甚至可能會(huì)接近用于計(jì)算本身的開(kāi)銷(xiāo),這就會(huì)造成嚴(yán)重的浪費(fèi)

2、協(xié)程的復(fù)蘇

為什么內(nèi)核線程調(diào)度切換成本高?
內(nèi)核線程的調(diào)度成本主要來(lái)自于用戶態(tài)與內(nèi)核態(tài)之間的狀態(tài)轉(zhuǎn)換,而這兩種狀態(tài)轉(zhuǎn)換的開(kāi)銷(xiāo)主要來(lái)自于響應(yīng)中斷、保護(hù)和恢復(fù)執(zhí)行現(xiàn)場(chǎng)的成本

協(xié)程的主要優(yōu)勢(shì)是輕量。在64位Linux上HotSpot的線程棧容量默認(rèn)是1MB,此外內(nèi)核數(shù)據(jù)結(jié)構(gòu)還會(huì)額外消耗16KB內(nèi)存。與之相對(duì)的,一個(gè)協(xié)程的棧通常在幾百個(gè)字節(jié)到幾KB之間,所以Java虛擬機(jī)里線程池容量達(dá)到兩百就已經(jīng)不算小了,而很多支持協(xié)程的應(yīng)用中,同時(shí)并存的協(xié)程數(shù)量可數(shù)以十萬(wàn)計(jì)

協(xié)程的局限是需要在應(yīng)用層面實(shí)現(xiàn)的內(nèi)容特定多

3、Java的解決方案


六、附錄

總結(jié)

以上是生活随笔為你收集整理的深入理解Java虚拟机(周志明第三版)- 第十二章:Java内存模型与线程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。