深入理解Java虚拟机(周志明第三版)- 第十二章:Java内存模型与线程
系列文章目錄
第一章: 走近Java
 第二章: Java內(nèi)存區(qū)域與內(nèi)存溢出異常
 第三章: Java垃圾收集器與內(nèi)存分配策略
并發(fā)處理的廣泛應(yīng)用是Amdahl定律代替摩爾定律成為計算機性能發(fā)展源動力的根本原因,也是人類壓榨計算機運算能力的最有力武器
- 系列文章目錄
- 一、概述
- 二、硬件的效率和一致性
- 三、Java內(nèi)存模型
- 1、主內(nèi)存與工作內(nèi)存
- 2、內(nèi)存間交互
- 3、對于volatile型變量的特殊規(guī)則
- 4、針對long和double變量的特殊規(guī)則
- 5、原子性、可見性與有序性
- 原子性
- 可見性
- 有序性
 
- 6、先行發(fā)生規(guī)則
 
- 四、Java與線程
- 1、線程的實現(xiàn)
- 使用內(nèi)核線程實現(xiàn)(1:1實現(xiàn))
- 使用用戶線程實現(xiàn)(1:N實現(xiàn))
- 使用用戶線程加輕量級進程混合實現(xiàn)(N:M實現(xiàn))
- Java線程如何實現(xiàn)
 
- 2、Java線程調(diào)度
- 3、狀態(tài)轉(zhuǎn)換
 
- 五、Java與協(xié)程(了解即可)
- 1、內(nèi)核線程的局限
- 2、協(xié)程的復(fù)蘇
- 3、Java的解決方案
 
- 六、附錄
一、概述
????????多任務(wù)處理是現(xiàn)代計算機幾乎必備的功能,不僅是因為計算機的運算能力強大了,還有一個很重要的原因是計算機的運算速度與它的存儲和通信子系統(tǒng)的速度差距太大,大量的時間花費在磁盤IO、網(wǎng)絡(luò)通信或數(shù)據(jù)庫訪問上,為了不希望處理器在大部分時間里都處于等待其他資源的空閑狀態(tài),就必須使用一些手段壓榨處理器的運算能力,否則會造成很大的性能浪費,而讓計算機同時處理幾項任務(wù)則是最容易想到的,也被證明是非常有效的壓榨手段。
????????另外一種更具體的并發(fā)應(yīng)用場景就是一個服務(wù)端同時對多個客戶端提供服務(wù)。衡量一個服務(wù)性能的好壞,每秒事務(wù)處理數(shù)(TPS)是重要的指標(biāo)之一,它代表著一秒內(nèi)服務(wù)端平均能響應(yīng)的請求總數(shù),而TPS與程序的并發(fā)能力有非常密切的關(guān)系。對于計算量相同的任務(wù),線程并發(fā)協(xié)調(diào)越有條不紊,效率越高;線程間競爭頻繁,互相阻塞甚至死鎖,將會大大降低程序并發(fā)能力。
????????服務(wù)端應(yīng)用是Java語言擅長的領(lǐng)域之一。而Java語言和虛擬機提供了許多工具,將并發(fā)編程的門檻降低了不少,且各種中間件、各類框架等也可能隱藏線程并發(fā)細節(jié),使得程序員在編碼時更關(guān)注業(yè)務(wù)邏輯。
但是無論語言、中間件和框架再如何先進,開發(fā)人員都不應(yīng)期望它們能獨立完成所有并發(fā)處理的事情,了解并發(fā)的內(nèi)幕仍然是成為一個高級程序員不可缺少的課程。
二、硬件的效率和一致性
什么是高速緩存?為什么需要高速緩存
 ????????物理機絕大多數(shù)的計算任務(wù)都不可能只靠處理器計算完成,處理器至少要與內(nèi)存交互,如讀取運算數(shù)據(jù)、存儲運算結(jié)果等,無法僅僅依靠寄存器完成所運算任務(wù)。由于計算機的存儲設(shè)備與處理器的運算速度有幾個數(shù)量級的差距,所以現(xiàn)代計算機不得不加入一層或多層讀寫速度盡可能接近處理器運算速度的高速緩存來作為內(nèi)存與處理器之間的緩沖:將運算需要使用的數(shù)據(jù)復(fù)制到緩存中,讓運算能快速進行,當(dāng)運算結(jié)束后再從緩存同步回內(nèi)存中,這樣處理器就無須等待緩慢的內(nèi)存讀寫了。
什么是緩存一致性問題?如何解決?
 在多路處理器系統(tǒng)中,每個處理器都有自己的高速緩存,而它們又共享同一主內(nèi)存,這種系統(tǒng)成為共享多核系統(tǒng),當(dāng)多個處理器的運算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時,將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致,如果發(fā)生這種情況,那同步回到主內(nèi)存時該以誰的緩存數(shù)據(jù)為準(zhǔn)呢?為了解決一致性問題,需要各個處理器訪問緩存時遵循一些協(xié)議,在讀寫時要根據(jù)協(xié)議來進行操作,這類協(xié)議有MSI、MESI等。
 
內(nèi)存模型可以理解為在特定的操作協(xié)議下,對特定的內(nèi)存或高速緩存進行讀寫訪問的過程抽象。不同架構(gòu)的物理機器可以擁有不一樣的內(nèi)存模型。
除了增加高速緩存之外,為了使處理器內(nèi)部的運算單元能盡量被充分使用,處理器可能會對輸入代碼進行亂序執(zhí)行優(yōu)化,處理器會在計算之后將亂序執(zhí)行的結(jié)果重組,保證該結(jié)果與順序執(zhí)行的結(jié)果是一致的,但并不保證程序中各個語句計算的先后順序與輸入代碼中的順序一致,因此如果存在一個計算任務(wù)依賴另外一個計算任務(wù)的中間結(jié)果,那么其順序性并不能靠代碼的先后順序來保證。與處理器的亂序執(zhí)行優(yōu)化類似,Java虛擬機的即時編譯器也有指令重排序優(yōu)化。
三、Java內(nèi)存模型
Java內(nèi)存模型屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓Java程序在各種平臺下都能達到一致的內(nèi)存訪問效果。
1、主內(nèi)存與工作內(nèi)存
????????Java內(nèi)存模型的主要目的是定義了程序中各種變量的訪問規(guī)則,即關(guān)注在虛擬機中把變量值存儲到內(nèi)存和從內(nèi)存中取出變量值這樣的底層操作細節(jié)。這里的變量包括了實例字段、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但是不包括局部變量與方法參數(shù),因為后者是線程私有的,不會被共享,自然就不存在競爭問題,且為了獲得更好的執(zhí)行效能,Java內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的特定寄存器或緩存來和主內(nèi)存進行交互,也沒有限制即時編譯器是否需要進行調(diào)整代碼執(zhí)行順序這類優(yōu)化措施。
Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存中,每個線程還有自己的工作內(nèi)存(類比高速緩存),線程的工作內(nèi)存中保存了該線程使用的變量的主內(nèi)存副本,線程對變量的所有讀寫操作都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的數(shù)據(jù),不同線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量值的傳遞都需要通過主內(nèi)存來完成。如圖:
 
2、內(nèi)存間交互
????????關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存這一類的實現(xiàn)細節(jié)。Java內(nèi)存模型定義了以下8種操作來完成,Java虛擬機實現(xiàn)時必須保證下面提及的每一種操作都是原子的、不可再分的:
- lock(鎖定):作用于主內(nèi)存的變量,將一個變量標(biāo)識為一條線程獨占的狀態(tài)
- unlock(解鎖):作用于主內(nèi)存的變量,將一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定
- read(讀取):作用于主內(nèi)存的變量,將一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load使用
- load(載入):作用于工作內(nèi)存的變量,將read操作讀取的變量值放入工作內(nèi)存的變量副本中
- use(使用):作用于工作內(nèi)存的變量,將工作內(nèi)存中變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機遇到需要使用變量值的字節(jié)碼指令時將執(zhí)行該操作
- assign(賦值):作用于工作內(nèi)存的變量,將從執(zhí)行引擎接收的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機遇到給變量賦值的字節(jié)碼指令時執(zhí)行該操作
- store(存儲):作用于工作內(nèi)存的變量,將工作內(nèi)存中一個變量的值傳送到主內(nèi)存中,以便隨后的write使用
- write(寫入):作用于主內(nèi)存的變量,將store操作從工作內(nèi)存取到的變量值放入主內(nèi)存的變量中
如果將一個變量從主內(nèi)存拷貝到工作內(nèi)存中,就要按順序執(zhí)行read和load操作,如果把變量從工作內(nèi)存同步回主內(nèi)存,就要按順序執(zhí)行store和write操作。Java內(nèi)存模型只要求上述兩個操作必須順序執(zhí)行,但不要求是連續(xù)執(zhí)行,即可能:read:a read:b load:b load:a。除此之外,Java內(nèi)存模型還規(guī)定了在執(zhí)行上述8種基本操作時必須滿足如下規(guī)則:
- 不允許read和load、store和write單一出現(xiàn),即不允許一個變量從主內(nèi)存讀取了但工作內(nèi)存不接收,或工作內(nèi)存發(fā)起回寫但主內(nèi)存不接收的情況
- 不允許一個線程丟失它最近的assign操作,即變量在工作內(nèi)存中改變后必須把變化同步回主內(nèi)存
- 不允許一個線程無原因地把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中
- 一個新的變量只能在主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個未經(jīng)初始化的變量,即對一個變量use、store操作前,必須先執(zhí)行assign和load操作
- 一個變量在同一時刻只允許一條線程對其進行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock,只有執(zhí)行相同次數(shù)的unlock操作,變量才會解鎖
- 如果對一個變量執(zhí)行l(wèi)ock操作,那將會清空工作內(nèi)存中此變量的值,執(zhí)行引擎使用這個變量值前,需要重新執(zhí)行l(wèi)oad或assign操作以初始化變量的值
- 如果一個變量事先沒有被lock操作鎖定,那就不允許對它執(zhí)行unlock操作,也不允許unlock一個被其他線程鎖定的變量
- 對一個變量執(zhí)行unlock操作前,必須先把此變量同步回主內(nèi)存中,
3、對于volatile型變量的特殊規(guī)則
關(guān)鍵字volatile是虛擬機提供的最輕量級的同步機制。
變量修飾為volatile有什么用?
 1、保證此變量對所有線程的可見性,即當(dāng)一條線程修改了這個變量的值,新值對于其他線程來說是可以立即得知的,而普通變量的值在線程間傳遞時均需要通過主內(nèi)存來完成,例線程A修改一個普通變量的值后,然后向主內(nèi)存進行回寫,另外一條線程B在線程A回寫完成后再對主內(nèi)存進行讀取操作,新變量值才對線程B可見。
volatile變量在各個線程的工作內(nèi)存中是不存在一致性問題的(物理存儲角度看,各個線程的工作內(nèi)存中volatile變量也可以存在不一致的情況,但由于每次使用之前都要先刷新,執(zhí)行引擎看不到不一致的情況,因此可以認(rèn)為不存在一致性問題,但是Java里運算操作符并非原子操作(自增++、自減–等),這導(dǎo)致volatile變量的運算在并發(fā)下一樣是不安全的)
 ????????
 由于volatile變量只能保證可見性,在不符合以下兩條規(guī)則的運算場景下,仍然要通過加鎖(synchronized、current并發(fā)包中的鎖或原子類)來保證原子性:
 ????????-運算結(jié)果不依賴變量的當(dāng)前值,或者確保只有單一的線程修改變量值
 ????????-變量不需要與其他的狀態(tài)變量共同參與不變約束
2、禁止指令重排序優(yōu)化,普通的變量僅會保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致
4、針對long和double變量的特殊規(guī)則
什么是long和double的非原子性協(xié)定?
 ????????Java內(nèi)存模型要求lock、unlock、read、load、assign、use、store、write這8種操作都具有原子性,但是對于64位的數(shù)據(jù)類型,在模型中定義了一條寬松的規(guī)定:允許虛擬機將沒有被volatile修飾的64位數(shù)據(jù)類型的讀寫操作劃分為兩次32位的操作來進行,即允許虛擬機實現(xiàn)自行選擇是否要保證64位數(shù)據(jù)類型的load、store、read、write這4個操作的原子性。
5、原子性、可見性與有序性
原子性
由Java內(nèi)存模型直接保證的原子性變量操作包括read、load、assign、use、store和write這6個,大致可以認(rèn)為,基本數(shù)據(jù)類型的訪問、讀寫都是原子性的,此外更大范圍的原子性保證,Java內(nèi)存模型還提供了lock和unlock操作來滿足需求,如更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式地使用,這兩個字節(jié)碼指令反映到Java代碼中就是同步塊-synchronized關(guān)鍵字
可見性
指當(dāng)一個線程修改了變量值后,其他線程能夠立即得知這個修改。volatile變量的特殊規(guī)則保證了新值能立即同步到主內(nèi)存中,以及每次使用前立即從主內(nèi)存刷新。
有序性
在本線程內(nèi)觀察,所有的操作都是有序的(線程內(nèi)似表現(xiàn)為串行的語義);如果在一個線程中觀察另一個線程,所有的操作都是無序的(指令重排序現(xiàn)象和工作內(nèi)存與主內(nèi)存同步延遲現(xiàn)象)。
6、先行發(fā)生規(guī)則
為什么有這個規(guī)則?
 如果Java內(nèi)存模型中所有的有序性都依靠volatile和synchronized來完成,那么將會有很多操作變得非常啰嗦,但是我們在編寫Java程序時并沒有察覺到這一點,就是因為Java語言中有一個先行發(fā)生原則
什么是先行發(fā)生?
 先行發(fā)生是Java內(nèi)存模型中定義的兩項操作之間的偏序關(guān)系。例如操作A先行發(fā)生于操作B,其實就是說在發(fā)生操作B之前,操作A產(chǎn)生的影響能夠被操作B觀察到,"影響"包括了修改內(nèi)存中共享變量的值、發(fā)送消息、調(diào)用了方法等。
什么是Java內(nèi)存模型的先行發(fā)生規(guī)則?
- 程序次序規(guī)則:在一個線程內(nèi),按照控制流順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作
- 管程鎖定規(guī)則:一個unlock操作先行發(fā)生于后面對同一個鎖的lock操作
- volatile變量規(guī)則:對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀操作
- 線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每個動作
- 線程終止規(guī)則:線程中所有操作都先行發(fā)生于對此線程的終止檢測
- 線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷的代碼檢測到中斷事件的發(fā)生
- 對象終結(jié)規(guī)則:一個對象的初始化操作先行發(fā)生于它的finalize()方法的開始
- 傳遞性:如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C,那么可以得出操作A先行發(fā)生于操作C
Java內(nèi)存模型的先行發(fā)生規(guī)則有什么用?
 滿足上述先行發(fā)生規(guī)則的場景無需使用任何同步手段保障。
四、Java與線程
1、線程的實現(xiàn)
線程是比進程更輕量級的調(diào)度執(zhí)行單元,線程的引入可以把一個進程的資源分配和執(zhí)行調(diào)度分開,各個線程既可以共享進程資源(內(nèi)存地址、文件IO等),又可以獨立調(diào)度。
實現(xiàn)線程主要有三種方式:
使用內(nèi)核線程實現(xiàn)(1:1實現(xiàn))
內(nèi)核線程指直接由操作系統(tǒng)內(nèi)核支持的線程,由內(nèi)核來完成線程切換,內(nèi)核通過操縱調(diào)度器對線程進行調(diào)度,并負責(zé)將線程的任務(wù)映射到各個處理器上。
輕量級進程是內(nèi)核線程的一種高級接口,也是我們通常意義講的線程,每一個輕量級進程都由一個內(nèi)核線程支持。
優(yōu)劣:
 每個輕量級進程都成為一個獨立的調(diào)度單元,即使其中一個輕量級進程阻塞了,也不會影響整個進程繼續(xù)工作。但由于是基于內(nèi)核線程實現(xiàn)的,各種線程操作需要進行系統(tǒng)調(diào)用,代價相對較高,需要在用戶態(tài)和內(nèi)核態(tài)來回切換,且一個輕量級進程對應(yīng)一個內(nèi)核線程,輕量級進程要消耗一定的內(nèi)核資源(內(nèi)核線程的棧空間),因此一個系統(tǒng)支持輕量級進程的數(shù)據(jù)是有限的
使用用戶線程實現(xiàn)(1:N實現(xiàn))
 用戶線程指完全建立在用戶空間的線程庫上,系統(tǒng)內(nèi)核不能感知到用戶線程的存在及如何實現(xiàn)的。用戶線程的創(chuàng)建、同步、銷毀、調(diào)度完全在用戶態(tài)中完成,不需要內(nèi)核的幫助。如果程序?qū)崿F(xiàn)得當(dāng),這種線程不需要切換到內(nèi)核態(tài),因此操作可以是非常快速且低消耗的,也能夠支持更大的線程數(shù)量。
優(yōu)劣:
 用戶線程的優(yōu)勢在于不需要系統(tǒng)內(nèi)核支援,劣勢也在于沒有系統(tǒng)內(nèi)核支援,所有的線程操作都需要用戶程序自己去處理,因此用戶線程實現(xiàn)的程序通常都比較復(fù)雜。
使用用戶線程加輕量級進程混合實現(xiàn)(N:M實現(xiàn))
 混合實現(xiàn)下,既存在用戶線程,也存在輕量級進程。用戶線程還是完全建立在用戶空間中,因此用戶線程的各種操作依然廉價,且可以支持大規(guī)模的用戶并發(fā)。而操作系統(tǒng)支持的輕量級進程則作為用戶線程和內(nèi)核線程之間的橋梁,這樣做可以使用內(nèi)核提供的線程調(diào)度功能及處理器映射,并且用戶線程的系統(tǒng)調(diào)用要通過輕量級進程來完成,大大降低了整個進程被完全阻塞的風(fēng)險。
Java線程如何實現(xiàn)
Java線程的實現(xiàn)并不受Java虛擬機規(guī)范的約束,這是一個與具體虛擬機相關(guān)的話題。
 目前主流平臺上的主流商用虛擬機的線程模型普遍使用基于操作系統(tǒng)原生線程模型實現(xiàn),即1:1實現(xiàn)模型
Hotspot虛擬機中,它的每一個Java線程都是直接映射到一個操作系統(tǒng)原生線程來實現(xiàn)的,而且中間沒有額外的間接結(jié)構(gòu),所以Hotspot是不會干涉線程調(diào)度的,全權(quán)交給底層的操作系統(tǒng)去處理。
2、Java線程調(diào)度
線程調(diào)度指系統(tǒng)為線程分配處理器使用權(quán)的過程,調(diào)度主要方式有兩種:協(xié)同式線程調(diào)度和搶占式線程調(diào)度。
協(xié)同式線程調(diào)度:
 指線程的執(zhí)行時間由線程本身控制,線程把自己的工作執(zhí)行完后,要主動通知系統(tǒng)切換到另外一個線程上去。協(xié)同式調(diào)度的好處是實現(xiàn)簡單且切換動作對自己本身是可知的,一般沒有什么線程同步的問題;壞處是線程執(zhí)行時間不可控,可能會一個線程阻塞影響整個進程或系統(tǒng)
搶占式線程調(diào)度:
 指每個線程的執(zhí)行時間由系統(tǒng)來分配,線程的切換不由線程本身決定。在這種實現(xiàn)線程調(diào)度方式下,線程的執(zhí)行時間是系統(tǒng)可控的,也不會因為一個線程導(dǎo)致整個進程或系統(tǒng)阻塞
Java使用的線程調(diào)度方式是搶占式線程調(diào)度。Java語言一共設(shè)置了10個級別的線程優(yōu)先級。當(dāng)兩個線程同時處于Ready狀態(tài)時,優(yōu)先級越高的線程越容易被系統(tǒng)選擇執(zhí)行(線程優(yōu)先級并不是一項穩(wěn)定的調(diào)節(jié)手段)。
3、狀態(tài)轉(zhuǎn)換
Java語言定義了6種線程狀態(tài),在任意一個時間點中,一個線程只能有且只有其中的一個狀態(tài),并且可以通過特定的方法在不同狀態(tài)之間轉(zhuǎn)換:
- 新建(New):創(chuàng)建后尚未啟動的線程處于這種狀態(tài)
- 運行(Runnable):包括操作系統(tǒng)線程狀態(tài)中的Running和Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著操作系統(tǒng)為它分配執(zhí)行時間
- 無限期等待(Waiting):處于這種狀態(tài)的線程不會被分配處理器執(zhí)行時間,它們要等待被其他線程顯式喚醒。以下方法會讓線程陷入無限期的等待狀態(tài):沒有設(shè)置Timeout參數(shù)的Object::wait()方法;沒有設(shè)置Timeout參數(shù)的Thread::join()方法;LockSupport::park()方法。
- 限期等待(Timed Waiting):處于這種狀態(tài)的線程也不會被分配處理器執(zhí)行時間,不過無須等待被其他線程顯式喚醒,在一定時間之后它們會由系統(tǒng)自動喚醒。以下方法會讓線程進入限期等待狀 態(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)”在等待著獲取到一個排它鎖,這個事件將在另外一個線程放棄這個鎖的時候發(fā)生;而“等待狀態(tài)”則是在等待一段時 間,或者喚醒動作的發(fā)生。在程序等待進入同步區(qū)域的時候,線程將進入這種狀態(tài)
- 結(jié)束(Terminated):已終止線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行
五、Java與協(xié)程(了解即可)
1、內(nèi)核線程的局限
目前Java線程面臨的困境:對Web應(yīng)用的服務(wù)要求,不論是在請求數(shù)量上還是在復(fù)雜度上,與十多年前相比已不可同日而語,這一方面是源于業(yè)務(wù)量的增長,另一方面來自于為了應(yīng)對業(yè)務(wù)復(fù)雜化而不斷進行的服務(wù)細分。現(xiàn)代B/S系統(tǒng)中一次對外部業(yè)務(wù)請求的響 應(yīng),往往需要分布在不同機器上的大量服務(wù)共同協(xié)作來實現(xiàn),這種服務(wù)細分的架構(gòu)在減少單個服務(wù)復(fù)雜度、增加復(fù)用性的同時,也不可避免地增加了服務(wù)的數(shù)量,縮短了留給每個服務(wù)的響應(yīng)時間。這要求每一個服務(wù)都必須在極短的時間內(nèi)完成計算,這樣組合多個服務(wù)的總耗時才不會太長;也要求每一個服務(wù)提供者都要能同時處理數(shù)量更龐大的請求,這樣才不會出現(xiàn)請求由于某個服務(wù)被阻塞而出現(xiàn)等待。
Java目前的并發(fā)編程機制就與上述架構(gòu)趨勢產(chǎn)生了一些矛盾,1:1的內(nèi)核線程模型是如今Java虛擬機線程實現(xiàn)的主流選擇,但是這種映射到操作系統(tǒng)上的線程天然的缺陷是切換、調(diào)度成本高昂,系統(tǒng)能容納的線程數(shù)量也很有限。以前處理一個請求可以允許花費很長時間在單體應(yīng)用中,具有這種線程切換的成本也是無傷大雅的,但現(xiàn)在在每個請求本身的執(zhí)行時間變得很短、數(shù)量變得很多的前提下, 用戶線程切換的開銷甚至可能會接近用于計算本身的開銷,這就會造成嚴(yán)重的浪費
2、協(xié)程的復(fù)蘇
為什么內(nèi)核線程調(diào)度切換成本高?
 內(nèi)核線程的調(diào)度成本主要來自于用戶態(tài)與內(nèi)核態(tài)之間的狀態(tài)轉(zhuǎn)換,而這兩種狀態(tài)轉(zhuǎn)換的開銷主要來自于響應(yīng)中斷、保護和恢復(fù)執(zhí)行現(xiàn)場的成本
協(xié)程的主要優(yōu)勢是輕量。在64位Linux上HotSpot的線程棧容量默認(rèn)是1MB,此外內(nèi)核數(shù)據(jù)結(jié)構(gòu)還會額外消耗16KB內(nèi)存。與之相對的,一個協(xié)程的棧通常在幾百個字節(jié)到幾KB之間,所以Java虛擬機里線程池容量達到兩百就已經(jīng)不算小了,而很多支持協(xié)程的應(yīng)用中,同時并存的協(xié)程數(shù)量可數(shù)以十萬計
協(xié)程的局限是需要在應(yīng)用層面實現(xiàn)的內(nèi)容特定多
3、Java的解決方案
六、附錄
總結(jié)
以上是生活随笔為你收集整理的深入理解Java虚拟机(周志明第三版)- 第十二章:Java内存模型与线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 周志明jvm第三版笔记-第一部分:第一章
- 下一篇: 周志明虚拟机最新版,大厂面试必备宝典
