精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

深入理解Java虛擬機(高效并發(fā))

云計算 虛擬化
高效并發(fā)是 JVM 系列的最后一篇,本篇主要介紹虛擬機如何實現(xiàn)多線程、多線程間如何共享和競爭數(shù)據(jù)以及共享和競爭數(shù)據(jù)帶來的問題及解決方案。

 高效并發(fā)是 JVM 系列的最后一篇,本篇主要介紹虛擬機如何實現(xiàn)多線程、多線程間如何共享和競爭數(shù)據(jù)以及共享和競爭數(shù)據(jù)帶來的問題及解決方案。

[[271680]]

一. Java 內(nèi)存模型與線程

讓計算機同時執(zhí)行多個任務(wù),不只是因為處理器的性能更加強大了,更重要是因為計算機的運算速度和它的存儲以及通信子系統(tǒng)速度差距太大,大量的時間都花費在磁盤 I/O 、網(wǎng)絡(luò)通信和數(shù)據(jù)庫訪問上。為了不讓處理器因為等待其它資源而浪費處理器的資源與時間,我們就必須采用讓計算機同時執(zhí)行多任務(wù)的方式去充分利用處理器的性能;同時也是為了應(yīng)對服務(wù)端高并發(fā)的需求。而 Java 內(nèi)存模型的設(shè)計和線程的存在正是為了更好、更高效的實現(xiàn)多任務(wù)。

1.硬件與效率的一致性

計算機中絕大多數(shù)的任務(wù)都不可能只靠處理器計算就能完成,處理器至少要和內(nèi)存交互,如讀取數(shù)據(jù)、存儲結(jié)果等等,這個 I/O 操作是很難消除的。由于計算器的存儲設(shè)備和處理器的運算速度有幾個量級的差距,所以計算機不得不加入一層讀寫速度盡可能接近處理器運算速度的高速緩存來作為內(nèi)存與處理器之間的緩沖:將運算需要用到的數(shù)據(jù)復(fù)制到緩存中,讓運算能快速進行,當(dāng)運算結(jié)束后再從緩存同步回內(nèi)存中,這樣處理器就無需等待緩慢的內(nèi)存讀寫了。

基于高速緩存的存儲交互很好的解決了處理器與內(nèi)存的速度矛盾,但是也為計算機系統(tǒng)帶來更高的復(fù)雜度,因為它引入了一個新的問題:緩存一致性。在多處理器中,每個處理器都有自己的高速緩存,而它們又共享同一主內(nèi)存。當(dāng)多個處理器的運算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時,將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致。為了解決一致性的問題,需要各個處理器的訪問緩存時都遵循一些協(xié)議,在讀寫時要根據(jù)協(xié)議來進行操作。

 

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

2.Java 內(nèi)存模型

Java 虛擬機規(guī)范中定義了 Java 內(nèi)存模型,用來屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓 Java 程序在各種平臺下都能達(dá)到一致的內(nèi)存訪問效果。像 C/C++ 這類語言直接使用了物理硬件和操作系統(tǒng)的內(nèi)存模型,因此會由于不同平臺上內(nèi)存模型的差異,需要針對不同平臺來編寫代碼。

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

Java 內(nèi)存模型的主要目標(biāo)是定義程序中各個變量的訪問規(guī)則,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中讀取變量這樣的底層細(xì)節(jié)。這里說的變量和 Java 代碼中的變量有所區(qū)別,它包括了實例字段、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但不包括變量和方法參數(shù),因為后者是線程私有的,不會被共享。為了獲得較好的執(zhí)行性能,Java 內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的特定寄存器或緩存來和主內(nèi)存進行交互,也沒有限制 JIT 編譯器進行代碼執(zhí)行順序這類優(yōu)化措施。

Java 內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存,每條線程都有自己單獨的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存的副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存,線程間變量值的傳遞均需要通過主內(nèi)存來完成。

 

內(nèi)存間交互操作

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

這 8 種操作分別是:lock(鎖定)、unlock(解鎖)、read(讀取)、load(載入)、use(使用)、assign(賦值)、store(存儲)、write(寫入)。

對 volatile 型變量的特殊規(guī)則

volatile 是 Java 虛擬機提供的最輕量級的同步機制。當(dāng)一個變量被定義為 volatile 后,它將具備兩種特性:

第一是保證此變量對所有線程的可見性,這里的「可見性」是指當(dāng)一條線程修改了這個變量的值,新值對于其他線程來說是可以立即得知的。普通變量則做不到這一點,需要通過主內(nèi)存來在線程間傳遞數(shù)據(jù)。比如,線程 A 修改了一個普通的變量值,然后向主內(nèi)存進行回寫,另一條線程 B 在 A 線程回寫完成之后再從主內(nèi)存進行讀寫操作,新變量值才會對線程 B 可見。

第二是禁止指令重排優(yōu)化。普通變量僅僅會保證方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方能夠獲取到正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致。因為在一個線程的方法執(zhí)行過程中無法感知到這點,這也就是 Java 內(nèi)存模型中描述的所謂的「線程內(nèi)表現(xiàn)為串行的語義」。

對 long 和 double 型變量的特殊規(guī)則

Java 內(nèi)存模型要求 lock、unlock、read、load、assign、use、store、writer 這 8 個操作都具有原子性,但對于 64 位數(shù)據(jù)類型(long 和 double),在模型中特別定義了一條相對寬松的規(guī)定:允許虛擬機將沒有被 volatile 修飾的 64 位數(shù)據(jù)的讀寫操作劃分為兩次 32 位的操作來進行,即允許虛擬機實現(xiàn)選擇可以不保證 64 位數(shù)據(jù)類型的 load、store、read 和 write 這 4 個操作的原子性。這點就是所謂的 long 和 double 的非原子協(xié)定。

如果有多個線程共享一個未聲明為 volatile 的 long 或 double 類型的變量,并且同時對它們進行讀取和修改操作,那么某些線程可能會讀取到一個錯誤的值。好在這種情況非常罕見,主流商業(yè)虛擬機中也都把對 long 和 double 的操作視為原子性,因此在實際開發(fā)中無需使用 volatile 來修飾變量。

原子性、可見性和有序性

Java 內(nèi)存模型是圍繞著在并發(fā)過程中如何處理原子性、可見性和有序性 3 個特質(zhì)來建立的。

原子性(Atomicity)

由 Java 內(nèi)存模型來直接保證原子性變量操作,包括 read、load、assign、use、store 和 write ,我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問讀寫是具備原子性的。如果應(yīng)用場景需要一個更大范圍的原子性保證,Java 內(nèi)存模型還提供了 lock 和 unlock 操作來滿足這種需求,盡管虛擬機未把 lock 和 unlock 操作直接開放給用戶使用,但是卻提供了更高層次的字節(jié)碼指令 monitorenter 和 monitorexit 來隱式地使用這兩個操作,這兩個字節(jié)碼指令反映到 Java 代碼中就是 synchronized 關(guān)鍵字,因此被 synchronize 修飾的方法或代碼塊之間的操作是具備原子性的。

可見性(Visibility)

可見性是指當(dāng)一個線程修改了共享變量的值,其它線程能夠立即得知這個修改。Java 內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來實現(xiàn)可見性的,無論是普通變量還是 volatile 變量都是如此,普通變量與 volatile 變量的區(qū)別是, volatile 的規(guī)則保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新。因此,可以說 volatile 保證了多線程操作變量的可見性,而普通變量則不能保證這一點。除了 volatile 外,Java 還有兩個關(guān)鍵字 synchronized 和 final 。synchronized 同步塊的可見性是由「對一個變量執(zhí)行 unlock 操作前,必須先把此變量同步回主內(nèi)存中(執(zhí)行 store、write 操作)」這條規(guī)則獲得的;final 的可見性是指“:被 final 修飾的字段在構(gòu)造器中一旦初始化完成,并且構(gòu)造器沒有「this」的引用傳遞出去,那在其他線程中就能看見 final 字段的值。

有序性(Ordering)

Java 程序中天然的有序性可以總結(jié)為:如果在本線程內(nèi),所有操作都是有序的;如果在一個線程中觀察另一個線程,所有的操作都是無序的。前半句是指「線程內(nèi)表現(xiàn)為串行的語義」,后半句是指「指令重排序」現(xiàn)象和「工作內(nèi)存和主內(nèi)存同步延遲」現(xiàn)象。Java 語言提供了 volatile 和 synchronized 兩個關(guān)鍵字來保證線程之間操作的有序性,volatile 關(guān)鍵字本身就包含了禁止指令重排的語義,而 synchronized 則是由「一個變量在同一時刻只允許一條線程對其進行 lock 操作」這條規(guī)則獲得的,這條規(guī)則決定了持有同一個鎖的兩個同步塊只能串行的進入。

先行發(fā)生原則

如果 Java 內(nèi)存模型中所有的有序性都僅僅靠 volatile 和 synchronized 來保證,那么有一些操作就會變得很繁瑣,但是我們在編寫 Java 并發(fā)代碼的時候并沒有感覺到這一點,這是因為 Java 語言中有一個「先行發(fā)生」(happens-before)原則。這個原則非常重要,它是判斷數(shù)據(jù)是否存在競爭、線程是否安全的主要依據(jù),依靠這個原則,我們可以通過幾條規(guī)則一攬子解決并發(fā)環(huán)境下兩個操作之間是否可能存在沖突的所有問題。

先行發(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ān)系,這些先行發(fā)生關(guān)系無需任何同步器協(xié)助就已存在,可以在編碼中直接使用。如果兩個兩個操作之間的關(guān)系不在此列,并且無法從下列規(guī)則推導(dǎo)出來,它們就沒有順序性保障,虛擬機就可以隨意的對它們進行重排序。

  • 程序次序規(guī)則:在一個線程內(nèi),按照程序代碼順序,寫在前面的代碼先行發(fā)生寫在后面的代碼。準(zhǔn)確的講,應(yīng)該是控制流順序而不是程序代碼順序,因為要考慮分支、循環(huán)等結(jié)構(gòu);
  • 管程鎖定規(guī)則:一個 unlock 操作先行發(fā)生于后面對于同一個鎖的 lock 操作;
  • volatile 變量規(guī)則:對一個 volatile 變量的寫操作先行發(fā)生于后面對這個變量的讀操作,理解了這個原則我們就能理解為什么 DCL 單例模式中為什么要用 volatile 來標(biāo)識實例對象了;
  • 線程啟動規(guī)則:線程的 start() 方法先行發(fā)生于此線程的所有其它動作;
  • 線程終止規(guī)則:線程中所有的操作都先行發(fā)生于對此線程的終止檢測;
  • 程序中斷規(guī)則:對線程 interrupt() 的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷時間的發(fā)生;
  • 對象終結(jié)規(guī)則:一個對象的初始化完成先行發(fā)生于它的 finalize() 的開始;
  • 傳遞性:操作 A 先行發(fā)生于 B,B 先行發(fā)生于 C,那么 A 就先行發(fā)生于 C。

3.Java 與線程

談?wù)?Java 中的并發(fā),通常都是和多線程相關(guān)的。這一小節(jié)我們就講講 Java 線程在虛擬機中的實現(xiàn)。

線程的實現(xiàn)

主流的操作系統(tǒng)都提供了線程實現(xiàn),Java 語言則提供了在不同硬件和操作系統(tǒng)平臺下對線程操作的統(tǒng)一處理,每個已經(jīng)執(zhí)行 start() 且還未結(jié)束的 Thread 類的實例就代表了一個線程。Thread 類所有關(guān)鍵方法都是 Native 的。Java API 中,一個 Native 方法往往意味著這個方法沒有使用或者無法使用平臺無關(guān)的手段來實現(xiàn)(當(dāng)然也可能是為了執(zhí)行效率而使用 Native 方法,不過,通常最高效率的手段就是平臺相關(guān)的手段)。

實現(xiàn)線程主要有 3 種方式:使用內(nèi)核線程實現(xiàn)、使用用戶線程實現(xiàn)、使用用戶線程加輕量級進程混合實現(xiàn)。

Java 線程的實現(xiàn)

Java 線程在 JDK 1.2 之前是基于稱為「綠色線程」的用戶線程實現(xiàn)的。而在 JDK 1.2 中,線程模型替換為基于操作系統(tǒng)原生線程模型來實現(xiàn)。因此,在目前的 JDK 版本中,操作系統(tǒng)支持怎樣的線程模型,在很大程度上決定了 Java 虛擬機的線程是怎樣映射的,這點在不同的平臺上沒有辦法達(dá)成一致,虛擬機規(guī)范中也沒有限定 Java 線程需要使用哪種線程模型來實現(xiàn)。線程模型只對線程的并發(fā)規(guī)模和操作成本產(chǎn)生影響,對 Java 程序的編碼和運行過程來說,這些差異都透明的。

4.Java 線程調(diào)度

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

協(xié)同式線程調(diào)度

如果是使用協(xié)同式調(diào)度的多線程系統(tǒng),線程的執(zhí)行時間由線程本身來控制,線程把自己的工作執(zhí)行完之后,要主動通知系統(tǒng)切換到另外一個線程上。協(xié)同式多線程的最大好處是實現(xiàn)簡單,而且由于線程要把自己的事情做完后才會進行線程切換,切換操作對線程自己是可知的,所有沒有線程同步的問題。但是它的壞處也很明顯:線程執(zhí)行時間不可控,甚至如果一個線程編寫有問題,一直不告訴操作系統(tǒng)進行線程切換,那么程序就會一直阻塞在那里。很久以前的 Windows 3.x 系統(tǒng)就是使用協(xié)同式來實現(xiàn)對進程多任務(wù),相當(dāng)不穩(wěn)定,一個進程堅持不讓出 CPU 執(zhí)行時間就可能導(dǎo)致整個系統(tǒng)崩潰。

搶占式線程調(diào)度

如果是使用搶占式調(diào)度的多線程系統(tǒng),那么每個線程將由系統(tǒng)來分配執(zhí)行時間,線程的切換不由線程本身來決定。在這種實現(xiàn)線程調(diào)度的方式下,線程的執(zhí)行實現(xiàn)是系統(tǒng)可控的,也不會有一個線程導(dǎo)致整個進程阻塞的問題,Java 使用的線程調(diào)度方式就是搶占式的。和前面所說的 Windows 3.x 的例子相對,在 Windows 9x/NT 內(nèi)核中就是使用搶占式來實現(xiàn)多進程的,當(dāng)一個進程出了問題,我們還可以使用任務(wù)管理器把這個進程「殺掉」,而不至于導(dǎo)致系統(tǒng)崩潰。

5.狀態(tài)轉(zhuǎn)換

Java 語言定義了 5 種線程狀態(tài),在任意一個時間點,一個線程只能有且只有其中一種狀態(tài),它們分別是:

  • 新建(New):創(chuàng)建后尚未啟動的線程處于這種狀態(tài);
  • 運行(Runnable):Runnable 包括了操作系統(tǒng)線程狀態(tài)中的 Running 和 Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著 CPU 為它分配執(zhí)行時間;
  • 無限期等待(Waiting):處于這種狀態(tài)的線程不會被分配 CPU 執(zhí)行時間,它們要等待被其它線程顯式地喚醒;以下三種方法會讓線程進入無限期等待狀態(tài):
    • 沒有設(shè)置 TimeOut 參數(shù)的 Object.wait();
    • 沒有設(shè)置 TimeOut 參數(shù)的 Thread.join();
    • LockSupport.park()。
  • 限期等待(Timed Waiting):處于這種狀態(tài)的線程也不會被分配 CPU 執(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):線程已經(jīng)結(jié)束執(zhí)行。

上述 5 中狀態(tài)遇到特定事件發(fā)生的時候?qū)ハ噢D(zhuǎn)換,如下圖:

 

二、線程安全與鎖優(yōu)化

本文的主題是高效并發(fā),但高效的前提是首先要保證并發(fā)的正確性和安全性,所以這一小節(jié)我們先從如何保證線程并發(fā)安全說起。

1.Java 線程安全

那么什么是線程安全呢?可以簡單的理解為多線程對同一塊內(nèi)存區(qū)域操作時,內(nèi)存值的變化是可預(yù)期的,不會因為多線程對同一塊內(nèi)存區(qū)域的操作和訪問導(dǎo)致內(nèi)存中存儲的值出現(xiàn)不可控的問題。

Java 語言中的線程安全

如果我們不把線程安全定義成一個非此即彼的概念(要么線程絕對安全,要么線程絕對不安全),那么我們可以根據(jù)線程安全的程度由強至弱依次分為如下五檔:

  • 不可變;
  • 絕對線程安全;
  • 相對線程安全;
  • 線程兼容;
  • 線程對立。

線程安全的實現(xiàn)方法

雖然線程安全與否與編碼實現(xiàn)有著莫大的關(guān)系,但虛擬機提供的同步和鎖機制也起到了非常重要的作用。下面我們就來看看虛擬機層面是如何保證線程安全的。

同步互斥

互斥同步是常見的一種并發(fā)正確性保障的手段。同步是指在多個線程并發(fā)訪問共享數(shù)據(jù)時,保證共享數(shù)據(jù)在同一時間只被一個線程使用。而互斥是實現(xiàn)同步的一種手段。Java 中最基本的互斥同步手段就是 synchronized 關(guān)鍵字,synchronized 關(guān)鍵字在經(jīng)過編譯之后,會在同步塊的前后分別形成 monitorenter 和 monitorexit 這兩個字節(jié)碼指令,這兩個字節(jié)碼都需要一個 reference 類型的參數(shù)來指明要鎖定和解鎖的對象。如果 Java 程序中的 synchronized 明確指明了對象參數(shù),那就是這個對象的 reference;如果沒有,那就根據(jù) synchronized 修飾的是實例方法還是類方法,去取對應(yīng)的對象實例或 class 對象來作為鎖對象。

根據(jù)虛擬機規(guī)范的要求,在執(zhí)行 monitorenter 指令時,首先要嘗試獲取對象的鎖。如果這個對象沒被鎖定,或者當(dāng)前線程已經(jīng)擁有了那個對象的鎖,就把鎖的計數(shù)器加 1;相應(yīng)的,在執(zhí)行monitorexit 指令時將鎖計數(shù)器減 1,當(dāng)鎖計數(shù)器為 0 時,鎖就被釋放。如果獲取鎖對象失敗,當(dāng)前線程就要阻塞等待,直到對象鎖被另一個線程釋放為止。

另外要說明的一點是,同步塊在已進入的線程執(zhí)行完之前,會阻塞后面其它線程的進入。由于 Java 線程是映射到操作系統(tǒng)原生線程之上的,如果要阻塞或者喚醒一個線程,都需要操作系統(tǒng)來幫忙完成,這就需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),線程狀態(tài)轉(zhuǎn)換需要耗費很多的處理器時間。對于簡單的同步塊(如被 synchronized 修飾的 getter() 和 setter() 方法),狀態(tài)轉(zhuǎn)換消耗的時間可能比用戶代碼消耗的時間還要長。所以 synchronized 是 Java 中一個重量級的操作,因此我們只有在必要的情況下才應(yīng)該使用它。當(dāng)然虛擬機本身也會做相應(yīng)的優(yōu)化,比如在操作系統(tǒng)阻塞線程前加入一段自旋等待過程,避免頻繁的用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)換過程。這一點我們在介紹鎖優(yōu)化的時候再細(xì)聊。

非阻塞同步

互斥同步最大的問題就是進行線程阻塞和喚醒所帶來的性能問題,因此這種同步也成為阻塞同步。從處理問題的方式上來說,互斥同步是一種悲觀的并發(fā)策略,認(rèn)為只要不去做正確的同步措施(例如加鎖),就肯定會出問題,無論共享數(shù)據(jù)是否會出現(xiàn)競爭,它都要進行加鎖(當(dāng)然虛擬機也會優(yōu)化掉一些不必要的鎖)。隨著硬件指令集的發(fā)展,我們有了另外一個選擇:基于沖突檢查的樂觀并發(fā)策略。通俗的說,就是先進行操作,如果沒有其他線程競爭,那操作就成功了;如果共享數(shù)據(jù)有其它線程競爭,產(chǎn)生了沖突,就采取其它的補救措施,這種樂觀的并發(fā)策略的許多實現(xiàn)都不需要把線程掛起,因此這種同步操作稱為非阻塞同步。

前面之所以說需要硬件指令集的發(fā)展,是因為我們需要操作和沖突檢測這兩個步驟具備原子性。

這個原子性靠什么來保證呢?如果這里再使用互斥同步來保證原子性就失去意義了,所以我們只能靠硬件來完成這件事,保證一個從語義上看起來需要多次操作的行為只通過一條處理器指令就能完成,這類指令常用的有:

  • 測試并設(shè)置(Test-and-Set)
  • 獲取并增加(Fetch-and-Increment)
  • 交換(Swap)
  • 比較并交換(Compare-and-Swap,簡稱 CAS)
  • 加載鏈接/條件存儲(Load-Linked/Store-Conditional,簡稱 LL/SC)

前三條是之前的處理器指令集里就有的,后兩條是新增的。

CAS 指令需要 3 個操作數(shù),分別是內(nèi)存位置(在 Java 中可以簡單理解為變量的內(nèi)存地址,用 V 表示)、舊的預(yù)期值(用 A 表示)和新值(用 B 表示)。CAS 執(zhí)行指令時,當(dāng)且僅當(dāng) V 符合舊預(yù)期值 A 時,處理器用新值 B 更新 V 的值,否則他就不執(zhí)行更新,但是無論是否更新了 V 的值,都會返回 V 的舊值,上述的處理過程是一個原子操作。

在 JDK 1.5 之后,Java 程序中才可以使用 CAS 操作,該操作由 sun.misc.Unsafe 類里的 compareAndSwapInt() 和 compareAndSwapLong() 等幾個方法包裝提供,虛擬機在內(nèi)部對這些方法做了特殊處理,即時編譯出來的結(jié)果就是一條平臺相關(guān)的處理器 CAS 指令,沒有方法的調(diào)用過程,或者可以認(rèn)為是無條件內(nèi)聯(lián)進去了。

由于 Unsafe 類不是提供給用戶程序調(diào)用的類,因此如果不用反射,我們只能通過其他的 Java API 來間接使用,比如 J.U.C 包里的整數(shù)原子類,其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 類的 CAS 操作。

盡管 CAS 看起來很美,但是這種操作卻無法覆蓋互斥同步的所有場景,并且 CAS 從語義上來說并不是完美的。如果一個變量 V 初次讀取的時候是 A 值,并且在準(zhǔn)備賦值的時候檢查它仍然是 A 值,那我們就能說它的值沒有被其他線程修改過嗎?如果在這段時間內(nèi)曾經(jīng)被改為了 B,后來又被改回為 A,那 CAS 操作就會認(rèn)為它從來沒有被改變過。這個漏洞稱為 CAS 操作的「ABA」問題。

為了解決「ABA」問題,J.U.C 包提供了一個帶有標(biāo)記的原子引用類 AtomicStamoedReference,它可以通過控制變量值的版本來保證 CAS 的正確性。不過這個類比較「雞肋」,大部分情況下 ABA 問題不會影響程序并發(fā)的正確性,如果需要解決 ABA 問題,改用傳統(tǒng)的互斥同步可能會比原子類更高效。

無同步方案

要保證線程安全不一定要進行同步,如果一個方法本來就不涉及共享數(shù)據(jù),那它自然無需任何同步措施,因此會有一些代碼天生就是線程安全的,其中就包括下面要說的可重入代碼和線程本地存儲。

可重入代碼(Reentrant Code):也叫純代碼,可以在代碼執(zhí)行的任何時候中斷它,轉(zhuǎn)而去執(zhí)行另一端代碼(包括遞歸調(diào)用自己),而在重新獲得控制權(quán)后,原來的程序不會出現(xiàn)任何錯誤。可重入代碼有一些共同特征,例如不依賴存儲在堆上的數(shù)據(jù)和公用的系統(tǒng)資源,用到的狀態(tài)量都由參數(shù)傳入、不調(diào)用非可重入的方法等。如果一個方法的返回結(jié)果可以預(yù)測,只要輸入相同,就能返回相同的輸出,那它就是可重入代碼,當(dāng)然也就是線程安全的。

線程本地存儲(Thread Local Storage):也就是說這個數(shù)據(jù)是線程獨有的,ThreadLocal 就是用來實現(xiàn)線程本地存儲的。

2.鎖優(yōu)化

HotSpot 虛擬機開發(fā)團隊花費了很大的精力實現(xiàn)了各種鎖優(yōu)化,比如自旋鎖與自適應(yīng)自旋、鎖消除、鎖粗化、輕量級鎖、偏向鎖等。

自旋鎖與自適應(yīng)自旋

自旋鎖前面我們在聊互斥同步的時候就提到過,互斥同步對性能最大的影響就是阻塞的實現(xiàn),掛起線程和恢復(fù)線程都涉及到了用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)換,這種狀態(tài)的轉(zhuǎn)換會給系統(tǒng)并發(fā)性能帶來很大的壓力。但是大多數(shù)場景下,共享數(shù)據(jù)的鎖定狀態(tài)只會持續(xù)很短的一段時間,為了這短暫的時間去掛起和恢復(fù)線程顯得不那么劃算。如果物理機有一個以上的處理器,能讓兩個或以上的線程同時并行處理,我們就可以讓后面請求鎖的那個線程「稍等一下」,但是不放棄處理器的執(zhí)行時間,看看持有鎖的線程是否很快就會釋放鎖。為了讓線程等待,我們只需要執(zhí)行一個空轉(zhuǎn)的循環(huán)(自旋),這就是所謂的自旋鎖。

自旋等待雖然避免了線程切換的開銷,但是它要占用處理器的時間。如果鎖被占用的時間很短,那么自旋等待的效果當(dāng)然很好;反之,如果鎖被占用的時間很長,那么自旋的線程就會白白消耗處理器資源,反而形成負(fù)優(yōu)化。所以自旋等待必須有個限度,但是這個限度如果設(shè)置一個固定值并不是最有選擇,因此虛擬機開發(fā)團隊設(shè)計了自適應(yīng)自旋鎖,讓自旋等待的時間不再固定,而是由前一次在同一個鎖上自旋的時間及鎖的擁有者的狀態(tài)來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運行,那么虛擬機就會認(rèn)為這次自旋也有可能會成功,會將自旋等待的時間延長。如果對于某個鎖,自旋等待很少成功獲得過,那在以后要獲取這個鎖的時候就會放棄自旋。有了自適應(yīng)自旋,隨著程序運行和性能監(jiān)控信息的不斷完善,虛擬機對程序鎖的狀況預(yù)測就會越來越準(zhǔn)確。

鎖消除

即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數(shù)據(jù)競爭的鎖就會進行鎖消除。所消除的主要判定依據(jù)來源于逃逸分析的數(shù)據(jù)支持,如果判定一段代碼中,堆上的所有數(shù)據(jù)都不會逃逸出去從而被其它線程訪問到,那就可以把它們當(dāng)做棧上數(shù)據(jù)對待,認(rèn)為它們是線程私有的,同步加鎖自然就沒必要了。

鎖粗化

我們在編碼時,總是推薦將同步塊的作用范圍限制到最小,只在共享數(shù)據(jù)的實際作用域中才進行同步,這樣是為了使得需要的同步操作數(shù)量盡可能變小,如果存在競爭,那等待鎖的線程也能盡快拿到鎖。通常,這樣做是正確的,但是如果一系列的連續(xù)操作都對同一個對象反復(fù)加鎖和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體中,那即使沒有線程競爭,頻繁的進行互斥同步也會導(dǎo)致不必要的性能損耗。那加鎖出現(xiàn)在循環(huán)體中來舉例,虛擬機遇到這種情況,就會把加鎖同步的范圍擴展(粗化)到循環(huán)體外,這樣只要加鎖一次就可以了,這就是鎖粗化。

關(guān)于輕量級鎖和偏向鎖這里就不再介紹,如果大家有興趣可以留言反饋,我在單獨發(fā)文介紹。

責(zé)任編輯:武曉燕 來源: BaronTalk
相關(guān)推薦

2012-11-14 09:57:46

JavaJava虛擬機JVM

2024-03-29 11:42:21

Java虛擬機

2024-04-03 13:49:00

Java虛擬機方法區(qū)

2016-09-01 12:37:13

OpenStack虛擬機Metadata

2024-03-26 07:30:07

Java虛擬機源文件

2023-09-22 23:00:11

Java虛擬機

2019-12-31 10:45:30

JavaVisualVM高并發(fā)

2024-04-10 07:40:45

Java虛擬機內(nèi)存

2024-12-31 09:00:12

Java線程狀態(tài)

2020-12-11 07:32:45

編程ThreadLocalJava

2020-11-13 08:42:24

Synchronize

2022-10-12 07:53:46

并發(fā)編程同步工具

2011-12-28 13:24:47

JavaJVM

2018-03-22 18:30:22

數(shù)據(jù)庫MySQL并發(fā)控制

2011-12-28 13:38:00

JavaJVM

2021-09-18 06:56:01

JavaCAS機制

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2020-07-21 08:26:08

SpringSecurity過濾器

2010-06-01 15:25:27

JavaCLASSPATH

2024-05-24 14:35:49

點贊
收藏

51CTO技術(shù)棧公眾號

日韩免费一区| 九七久久人人| 日韩中文字幕亚洲一区二区va在线 | 亚洲成人自拍网| 国产一区二区自拍| 亚洲欧美一二三区| 久久综合国产| 日韩手机在线导航| 少妇高潮毛片色欲ava片| 无码国产伦一区二区三区视频| 销魂美女一区二区三区视频在线| 亚洲欧美国产日韩天堂区| 看欧美ab黄色大片视频免费| 99reav在线| 国产成人在线看| 91精品国产免费久久久久久| 精品人妻一区二区三区蜜桃视频| 精品福利在线| 亚洲欧美日韩国产综合在线| eeuss一区二区三区| 中文字幕在线播| 牛夜精品久久久久久久99黑人| 日韩av在线免费观看| 日日躁夜夜躁aaaabbbb| 日本精品600av| 国产亚洲一区字幕| 5g影院天天爽成人免费下载| 在线观看亚洲天堂| 欧美xxxx中国| 亚洲精品国产精品久久清纯直播| 在线免费观看av的网站| 77thz桃花论族在线观看| 国产拍揄自揄精品视频麻豆| 成人av播放| 国产成人a v| 久久精品一区二区不卡| 精品亚洲国产成av人片传媒| 欧美国产日韩在线播放| 91精品国产综合久久久久久豆腐| 成人午夜又粗又硬又大| 国产乱肥老妇国产一区二| 日本一二三区在线观看| 亚洲毛片免费看| 欧美日韩久久久一区| 亚洲熟妇av一区二区三区漫画| 免费福利在线视频| 成人18视频在线播放| 国产精品永久免费| 在线天堂中文字幕| 欧美午夜在线| 久久婷婷国产麻豆91天堂| 蜜桃无码一区二区三区| 久久365资源| 日韩欧美另类在线| 黄色av免费在线播放| 91超碰在线播放| 一区二区三区欧美视频| 亚洲亚洲精品三区日韩精品在线视频 | 日p在线观看| 国产亚洲精品免费| 欧美三日本三级少妇三99| 亚洲精品无码久久久| 国产一区二区三区免费观看| 国产精品直播网红| 精品视频一二三区| 男女精品视频| 国产不卡av在线| 国产高潮久久久| 亚洲精品资源| 91精品国产高清自在线看超| 一区二区三区视频免费看| 精品1区2区3区4区| 欧美精品精品精品精品免费| 久草视频在线免费看| 2023国产精品久久久精品双| 日韩视频精品在线| 日本精品在线免费观看| 亚洲国产精品成人| 欧美区在线播放| 免费中文字幕在线观看| 亚洲小说欧美另类婷婷| 91国在线精品国内播放| 日本一区二区三区精品| 天堂蜜桃91精品| 国产精品久久久久一区二区| 一级黄色大毛片| 国产乱对白刺激视频不卡| 91在线视频免费| 中文字幕在线播放av| 日韩精品一级二级| 国产精品视频中文字幕91| ,亚洲人成毛片在线播放| 国精产品一区一区三区mba桃花 | 成人av资源在线观看| 精品国产一区二区三区四区精华| 亚洲色偷精品一区二区三区| 26uuu成人网一区二区三区| 日本一区二区久久精品| 麻豆国产在线播放| 中文字幕综合网| 国产精品va在线观看无码| 欧美sm一区| 欧美天天综合网| 99re6在线观看| 66精品视频在线观看| 日韩极品精品视频免费观看| 国产jk精品白丝av在线观看| 五月开心六月丁香综合色啪| 欧美激情va永久在线播放| 中国一级免费毛片| 老司机精品视频导航| 97神马电影| 久久综合九色综合久| 亚洲欧洲精品天堂一级| 欧美国产日韩激情| 成人在线观看免费视频| 日韩女优电影在线观看| 成都免费高清电影| 牛夜精品久久久久久久99黑人| 7m第一福利500精品视频| 欧美三级小视频| 国产精品日韩| 国产成人av一区二区三区| 日韩在线免费电影| 欧美日韩一二三四五区| 中文字幕人妻熟女人妻a片| 欧美少妇性xxxx| 日本午夜在线亚洲.国产| 亚洲黄色小说网址| 亚洲色欲色欲www在线观看| 黄色国产小视频| 色综合www| 久久久久久久久久久人体| 一区二区三区免费在线视频| 国产欧美视频在线观看| 免费观看日韩毛片| 国产精品x8x8一区二区| 久久天天躁狠狠躁夜夜躁| 国产精品成人久久久| 久久综合999| 国产免费观看高清视频| 精品久久对白| 欧美国产日韩一区二区三区| 国产三级三级在线观看| 国产精品黄色在线观看| 五月天婷婷激情视频| 亚洲香蕉视频| 日韩免费av一区二区| 婷婷婷国产在线视频| 亚洲精品视频免费看| 在线观看岛国av| 99re久久最新地址获取| 国产精品久久久久不卡| 国产精品二线| 欧美日韩一级二级三级| 羞羞在线观看视频| 久热成人在线视频| 中文字幕日韩精品一区二区| 9999精品视频| 欧美国产精品va在线观看| 黄色美女一级片| 午夜精品爽啪视频| v8888av| 青娱乐精品视频| 亚洲一区三区电影在线观看| 一级欧美视频| 久久69精品久久久久久久电影好| www.久久综合| 亚洲成a人片在线不卡一二三区| 国产情侣久久久久aⅴ免费| 99香蕉国产精品偷在线观看 | 97视频在线观看网站| 欧美剧在线免费观看网站| 希岛爱理中文字幕| 国产91精品入口| 国产精品后入内射日本在线观看| 一区二区三区日本久久久| 国产精品色悠悠| а√天堂官网中文在线| 精品91自产拍在线观看一区| 精品人妻无码一区二区性色| 亚洲国产成人一区二区三区| 天堂网成人在线| 精品成人久久| 婷婷久久伊人| 亚洲精品观看| 欧美综合在线观看| 欧美成人hd| 亚洲国产黄色片| 中文字幕乱码视频| 亚洲自拍偷拍图区| 魔女鞋交玉足榨精调教| 精品一二线国产| 777777av| 91精品久久久久久久久久不卡| 九色综合日本| 97久久中文字幕| 欧洲日本亚洲国产区| 老司机在线看片网av| 亚洲精品久久视频| 在线观看色网站| 香蕉成人伊视频在线观看| 99久久久无码国产精品衣服| 国产精品亚洲视频| 黄色片在线免费| 亚洲精品综合| 日本a级片在线观看| sdde在线播放一区二区| 国产乱码精品一区二区三区日韩精品| 91国内外精品自在线播放| 97精品国产97久久久久久免费| a√在线中文网新版址在线| 亚洲国产99精品国自产| 国产乱淫av片免费| 色先锋aa成人| 久久高清免费视频| 亚洲男女一区二区三区| 久久婷婷五月综合| 99九九99九九九视频精品| 中文国产在线观看| 日本不卡视频一二三区| 黄色片视频在线免费观看| 在线一区免费| 一区二区日本| 欧美少妇xxxx| 日韩精品久久一区| 亚洲97av| 免费一区二区三区| 欧美日韩破处| 国产一区二区中文字幕免费看| 日韩精品一级| 亚洲自拍偷拍一区| 性欧美video另类hd尤物| 国产精品99导航| 亚洲v.com| 91高清视频免费| 99热99re6国产在线播放| 欧美成人午夜激情视频| 很黄的网站在线观看| 日韩在线不卡视频| 日本中文字幕在线观看| 中文字幕国产亚洲2019| 国产精品久久久久一区二区国产| 亚洲欧美色婷婷| 欧美女优在线观看| 亚洲欧美福利视频| 黄色网址在线播放| 一本色道久久88精品综合| 黄色片在线看| 正在播放欧美视频| av电影在线网| 久久国产一区二区三区| 国产盗摄在线观看| 色综合色综合网色综合| free性欧美| 欧美一级在线亚洲天堂| 成人亚洲欧美| 国产精品综合久久久| 成人亚洲精品| 国产精品成人观看视频免费| 欧美三级自拍| 热舞福利精品大尺度视频| 日韩欧美视频专区| 久久天天东北熟女毛茸茸| 好看不卡的中文字幕| 国产午夜伦鲁鲁| 青青草97国产精品免费观看无弹窗版| 精品亚洲一区二区三区四区| 国产一区二区网址| 中文字幕免费高清视频| 久久久久成人黄色影片| 网站永久看片免费| 亚洲一区二区三区四区在线观看 | 午夜小视频在线观看| 国内自拍欧美激情| 精品国模一区二区三区| 成人欧美在线视频| 精品国产一区二区三区不卡蜜臂| 日本高清视频一区二区三区| 亚洲不卡av不卡一区二区| 欧美性潮喷xxxxx免费视频看| 香蕉久久国产| 可以看的av网址| 久久奇米777| 国产97免费视频| 欧美视频在线观看免费网址| 一级黄色片免费看| 亚洲国产天堂久久国产91 | 久久精品久久久久| 91av久久| 日韩av成人在线| 亚洲图色一区二区三区| 鲁鲁狠狠狠7777一区二区| 国产精品99久久精品| www.av片| 日本 国产 欧美色综合| 超碰caoprom| 欧美国产禁国产网站cc| 紧身裙女教师波多野结衣| 色综合久久久久久久久久久| 亚洲系列在线观看| 日韩电影网在线| 日本在线播放| 欧美一级黑人aaaaaaa做受| 57pao成人永久免费| 狠狠色综合网站久久久久久久| 成人3d动漫在线观看| av无码久久久久久不卡网站| 三级一区在线视频先锋| 亚洲综合中文网| 国产精品久久久久四虎| 青青草av在线播放| 欧美日韩国产中文| 亚洲av成人无码久久精品老人| 日韩在线不卡视频| 欧美日韩国产网站| 韩国成人av| 欧美日韩岛国| 亚洲精品成人在线播放| 久久在线观看免费| 日韩三级视频在线| 日韩三级精品电影久久久| 国产毛片av在线| 人人爽久久涩噜噜噜网站| 一区二区三区视频播放| 亚洲在线视频一区二区| 日本91福利区| 亚洲熟妇无码av| 日韩欧美亚洲范冰冰与中字| 成人av一区二区三区在线观看| 在线视频一区二区| 日韩精品三区| 久久99影院| 国产日韩一区| 中文字幕天堂网| 亚洲综合色区另类av| jlzzjlzzjlzz亚洲人| www.日韩.com| 中韩乱幕日产无线码一区| 久热这里只精品99re8久| 伊人久久亚洲美女图片| 无码任你躁久久久久久老妇| 亚洲六月丁香色婷婷综合久久| jizz国产在线观看| 亚洲性69xxxbbb| 向日葵视频成人app网址| 四虎永久国产精品| 美女黄网久久| 搡老熟女老女人一区二区| 日韩欧美国产视频| 性xxxx视频播放免费| 性欧美xxxx交| 国产厕拍一区| 精品少妇人欧美激情在线观看| 国产成人8x视频一区二区| 日本网站在线免费观看| 精品成人一区二区三区| 青青青草视频在线| 国内精品视频免费| 亚洲一区欧美二区| 中文字幕 自拍| 欧美三级中文字幕| 欧美大片aaa| 国产精品永久在线| 亚洲中无吗在线| 中文字幕无码人妻少妇免费| 婷婷丁香激情综合| 日韩一区二区三区不卡| 97免费视频在线| 亚洲激情77| 依人在线免费视频| 亚洲欧美经典视频| 色婷婷av一区二区三区之红樱桃 | 免费在线观看国产精品| 亚洲高清福利视频| 国产一区一一区高清不卡| 小说区图片区图片区另类灬| 国产一区二区精品久久99| 久久久精品视频在线| 日韩三级在线免费观看| 久久久久久久| 亚洲日本一区二区三区在线不卡| 国产精品中文欧美| 国产精品视频一区在线观看| 中文字幕成人在线| 久久香蕉精品香蕉| 人人干人人干人人| 亚洲不卡av一区二区三区| 成人午夜在线观看视频| 国产精品久久7| 麻豆成人综合网| 久久久久久久久久久久久久久久久| 亚洲男人天堂久| 91麻豆精品国产综合久久久| 777av视频| 国产精品福利电影一区二区三区四区| 免费观看国产精品| 国产精品久久久久久久久男 | 黄色网页免费在线观看| 欧美国产综合一区二区|