本篇文章給大家?guī)?lái)了關(guān)于java的相關(guān)知識(shí),其中主要整理了并發(fā)編程的相關(guān)問(wèn)題,包括了Java 內(nèi)存模型、volatile 詳解以及synchronized 的實(shí)現(xiàn)原理等等內(nèi)容,下面一起來(lái)看一下,希望對(duì)大家有幫助。
推薦學(xué)習(xí):《java視頻教程》
一、JMM 基礎(chǔ)-計(jì)算機(jī)原理
Java 內(nèi)存模型即 Java Memory Model,簡(jiǎn)稱JMM。JMM 定義了Java 虛擬機(jī) (JVM)在計(jì)算機(jī)內(nèi)存(RAM)中的工作方式。JVM 是整個(gè)計(jì)算機(jī)虛擬模型,所以 JMM 是隸屬于 JVM 的。Java1.5 版本對(duì)其進(jìn)行了重構(gòu),現(xiàn)在的 Java 仍沿用了 Java1.5 的版本。Jmm 遇到的問(wèn)題與現(xiàn)代計(jì)算機(jī)中遇到的問(wèn)題是差不多的。
物理計(jì)算機(jī)中的并發(fā)問(wèn)題,物理機(jī)遇到的并發(fā)問(wèn)題與虛擬機(jī)中的情況有不少 相似之處,物理機(jī)對(duì)并發(fā)的處理方案對(duì)于虛擬機(jī)的實(shí)現(xiàn)也有相當(dāng)大的參考意義。
根據(jù)《Jeff Dean 在 Google 全體工程大會(huì)的報(bào)告》我們可以看到
計(jì)算機(jī)在做一些我們平時(shí)的基本操作時(shí),需要的響應(yīng)時(shí)間是不一樣的。
以下案例僅做說(shuō)明,并不代表真實(shí)情況。
如果從內(nèi)存中讀取 1M 的 int 型數(shù)據(jù)由 CPU 進(jìn)行累加,耗時(shí)要多久?
做個(gè)簡(jiǎn)單的計(jì)算,1M 的數(shù)據(jù),Java 里 int 型為 32 位,4 個(gè)字節(jié),共有 1024*1024/4 = 262144 個(gè)整數(shù) ,則 CPU 計(jì)算耗時(shí):262144 0.6 = 157286 納秒, 而我們知道從內(nèi)存讀取 1M 數(shù)據(jù)需要 250000 納秒,兩者雖然有差距(當(dāng)然這個(gè)差距并不小,十萬(wàn)納秒的時(shí)間足夠 CPU 執(zhí)行將近二十萬(wàn)條指令了),但是還在 一個(gè)數(shù)量級(jí)上。但是,沒(méi)有任何緩存機(jī)制的情況下,意味著每個(gè)數(shù)都需要從內(nèi)存 中讀取,這樣加上 CPU 讀取一次內(nèi)存需要 100 納秒,262144 個(gè)整數(shù)從內(nèi)存讀取 到 CPU 加上計(jì)算時(shí)間一共需要 262144100+250000 = 26 464 400 納秒,這就存在 著數(shù)量級(jí)上的差異了。
而且現(xiàn)實(shí)情況中絕大多數(shù)的運(yùn)算任務(wù)都不可能只靠處理器“計(jì)算”就能完成,處理器至少要與內(nèi)存交互,如讀取運(yùn)算數(shù)據(jù)、存儲(chǔ)運(yùn)算結(jié)果等,這個(gè) I/O 操作是基本上是無(wú)法消除的(無(wú)法僅靠寄存器來(lái)完成所有運(yùn)算任務(wù))。早期計(jì)算機(jī)中 cpu 和內(nèi)存的速度是差不多的,但在現(xiàn)代計(jì)算機(jī)中,cpu 的指令速度遠(yuǎn)超內(nèi)存的存取速度,由于計(jì)算機(jī)的存儲(chǔ)設(shè)備與處理器的運(yùn)算速度有幾個(gè)數(shù)量級(jí)的差距,所 以現(xiàn)代計(jì)算機(jī)系統(tǒng)都不得不加入一層讀寫速度盡可能接近處理器運(yùn)算速度的高速緩存(Cache)來(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)存讀寫了。
在計(jì)算機(jī)系統(tǒng)中,寄存器是 L0 級(jí)緩存,接著依次是 L1,L2,L3(接下來(lái)是內(nèi)存,本地磁盤,遠(yuǎn)程存儲(chǔ))。越往上的緩存存儲(chǔ)空間越小,速度越快,成本也更高;越往下的存儲(chǔ)空間越大,速度更慢,成本也更低。從上至下,每一層都可以看做是更下一層的緩存,即:L0 寄存器是 L1 一級(jí)緩存的緩存,L1 是 L2 的緩存,依次類推;每一層的數(shù)據(jù)都是來(lái)至它的下一層,所以每一層的數(shù)據(jù)是下一 層的數(shù)據(jù)的子集。
在現(xiàn)代 CPU 上,一般來(lái)說(shuō) L0, L1,L2,L3 都集成在 CPU 內(nèi)部,而 L1 還分 為一級(jí)數(shù)據(jù)緩存(Data Cache,D-Cache,L1d)和一級(jí)指令緩存(Instruction Cache, I-Cache,L1i),分別用于存放數(shù)據(jù)和執(zhí)行數(shù)據(jù)的指令解碼。每個(gè)核心擁有獨(dú)立 的運(yùn)算處理單元、控制器、寄存器、L1、L2 緩存,然后一個(gè) CPU 的多個(gè)核心共 享最后一層 CPU 緩存 L3。
二、Java 內(nèi)存模型(JMM)
從抽象的角度來(lái)看,JMM 定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(Main Memory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(Local Memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本。本地內(nèi)存是 JMM 的一個(gè)抽象概念,并不真實(shí)存在。它涵蓋了緩存、寫緩沖區(qū)、寄存器以及其他的硬件和編譯器優(yōu)化。
2.1、可見(jiàn)性
可見(jiàn)性是指當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值, 其他線程能夠立即看得到修改的值。
由于線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量,那么對(duì)于共享變量 V,它們首先是在自己的工作內(nèi)存,之后再同步到主內(nèi)存??墒遣⒉粫?huì)及時(shí)的刷到主存中,而是會(huì)有一定時(shí)間差。很明顯,這個(gè)時(shí)候線程 A 對(duì)變量 V 的操作對(duì)于線程 B 而言就不具備可見(jiàn)性了 。
要解決共享對(duì)象可見(jiàn)性這個(gè)問(wèn)題,我們可以使用 volatile 關(guān)鍵字或者是加鎖。
2.2、原子性
原子性:即一個(gè)操作或者多個(gè)操作,要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行。
我們都知道 CPU 資源的分配都是以線程為單位的,并且是分時(shí)調(diào)用,操作系統(tǒng)允許某個(gè)進(jìn)程執(zhí)行一小段時(shí)間,例如 50 毫秒,過(guò)了 50 毫秒操作系統(tǒng)就會(huì)重新選擇一個(gè)進(jìn)程來(lái)執(zhí)行(我們稱為“任務(wù)切換”),這個(gè) 50 毫秒稱為“時(shí)間片”。 而任務(wù)的切換大多數(shù)是在時(shí)間片段結(jié)束以后,。
那么線程切換為什么會(huì)帶來(lái) bug 呢?
因?yàn)椴僮飨到y(tǒng)做任務(wù)切換,可以發(fā)生在任何一條 CPU 指令執(zhí)行完!注意,是 CPU 指令,CPU 指令,CPU 指令,而不是高級(jí)語(yǔ)言里的一條語(yǔ)句。比如 count++,在 java 里就是一句話,但高級(jí)語(yǔ)言里一條語(yǔ)句往往需要多條 CPU 指令完成。其實(shí) count++至少包含了三個(gè) CPU 指令!
三、volatile 詳解
3.1、volatile 特性
可以把對(duì) volatile 變量的單個(gè)讀/寫
,看成是使用同一個(gè)鎖對(duì)這些單個(gè)讀/寫
操作做了同步
public class Volati { // 使用volatile 聲明一個(gè)64位的long型變量 volatile long i = 0L;// 單個(gè)volatile 變量的讀 public long getI() { return i; }// 單個(gè)volatile 變量的寫 public void setI(long i) { this.i = i; }// 復(fù)合(多個(gè))volatile 變量的 讀/寫 public void iCount(){ i ++; }}
可以看成是下面的代碼:
public class VolaLikeSyn { // 使用 long 型變量 long i = 0L; public synchronized long getI() { return i; }// 對(duì)單個(gè)的普通變量的讀用同一個(gè)鎖同步 public synchronized void setI(long i) { this.i = i; }// 普通方法調(diào)用 public void iCount(){ long temp = getI(); // 調(diào)用已同步的讀方法 temp = temp + 1L; // 普通寫操作 setI(temp); // 調(diào)用已同步的寫方法 }}
所以 volatile 變量自身具有下列特性:
- 可見(jiàn)性:對(duì)一個(gè) volatile 變量的讀,總是能看到(任意線程)對(duì)這個(gè) volatile 變量最后的寫入。
- 原子性:對(duì)任意單個(gè) volatile 變量的讀/寫具有原子性,但類似于 volatile++ 這種復(fù)合操作不具有原子性。
volatile 雖然能保證執(zhí)行完及時(shí)把變量刷到主內(nèi)存中,但對(duì)于 count++這種非原子性、多指令的情況,由于線程切換,線程 A 剛把 count=0 加載到工作內(nèi)存, 線程 B 就可以開始工作了,這樣就會(huì)導(dǎo)致線程 A 和 B 執(zhí)行完的結(jié)果都是 1,都寫到主內(nèi)存中,主內(nèi)存的值還是 1 不是 2
3.2、volatile 的實(shí)現(xiàn)原理
- volatile 關(guān)鍵字修飾的變量會(huì)存在一個(gè)“l(fā)ock:”的前綴。
- Lock 前綴,Lock 不是一種內(nèi)存屏障,但是它能完成類似內(nèi)存屏障的功能。Lock 會(huì)對(duì) CPU 總線和高速緩存加鎖,可以理解為 CPU 指令級(jí)的一種鎖。
- 同時(shí)該指令會(huì)將當(dāng)前處理器緩存行的數(shù)據(jù)直接寫會(huì)到系統(tǒng)內(nèi)存中,且這個(gè)寫 回內(nèi)存的操作會(huì)使在其他 CPU 里緩存了該地址的數(shù)據(jù)無(wú)效。
四、synchronized 的實(shí)現(xiàn)原理
Synchronized 在 JVM 里的實(shí)現(xiàn)都是基于進(jìn)入和退出 Monitor 對(duì)象來(lái)實(shí)現(xiàn)方法同步和代碼塊同步,雖然具體實(shí)現(xiàn)細(xì)節(jié)不一樣,但是都可以通過(guò)成對(duì)的 MonitorEnter 和 MonitorExit 指令來(lái)實(shí)現(xiàn)。
對(duì)同步塊,MonitorEnter 指令插入在同步代碼塊的開始位置,而 monitorExit 指令則插入在方法結(jié)束處和異常處,JVM 保證每個(gè) MonitorEnter 必須有對(duì)應(yīng)的 MonitorExit??偟膩?lái)說(shuō),當(dāng)代碼執(zhí)行到該指令時(shí),將會(huì)嘗試獲取該對(duì)象 Monitor 的所有權(quán),即嘗試獲得該對(duì)象的鎖:
- 如果 monitor 的進(jìn)入數(shù)為 0,則該線程進(jìn)入 monitor,然后將進(jìn)入數(shù)設(shè)置為 1,該線程即為 monitor 的所有者。
- 如果線程已經(jīng)占有該 monitor,只是重新進(jìn)入,則進(jìn)入 monitor 的進(jìn)入數(shù)加 1。
- 如果其他線程已經(jīng)占用了 monitor,則該線程進(jìn)入阻塞狀態(tài),直到 monitor 的進(jìn)入數(shù)為 0,再重新嘗試獲取 monitor 的所有權(quán)。 對(duì)同步方法,從同步方法反編譯的結(jié)果來(lái)看,方法的同步并沒(méi)有通過(guò)指令 monitorenter 和 monitorexit 來(lái)實(shí)現(xiàn),相對(duì)于普通方法,其常量池中多了 ACC_SYNCHRONIZED 標(biāo)示符。
JVM 就是根據(jù)該標(biāo)示符來(lái)實(shí)現(xiàn)方法的同步的:當(dāng)方法被調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程將先獲取 monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放 monitor。在方法執(zhí)行期間,其他任何線程都無(wú)法再獲得同一個(gè) monitor 對(duì)象。
synchronized 使用的鎖是存放在 Java 對(duì)象頭里面,Java 對(duì)象的對(duì)象頭由 mark word 和 klass pointer 兩部分組成:
- mark word 存儲(chǔ)了同步狀態(tài)、標(biāo)識(shí)、hashcode、GC 狀態(tài)等等。
- klass pointer 存儲(chǔ)對(duì)象的類型指針,該指針指向它的類元數(shù)據(jù) 另外對(duì)于數(shù)組而言還會(huì)有一份記錄數(shù)組長(zhǎng)度的數(shù)據(jù)。
鎖信息則是存在于對(duì)象的 mark word 中,MarkWord 里默認(rèn)數(shù)據(jù)是存儲(chǔ)對(duì)象的 HashCode 等信息。
但是會(huì)隨著對(duì)象的運(yùn)行改變而發(fā)生變化,不同的鎖狀態(tài)對(duì)應(yīng)著不同的記錄存儲(chǔ)方式
4.1、鎖的狀態(tài)
對(duì)照上面的圖中,我們發(fā)現(xiàn)鎖一共有四種狀態(tài),無(wú)鎖狀態(tài),偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài), 它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí),目的是為了提高獲得鎖和 釋放鎖的效率。
4.2、偏向鎖
引入背景:大多數(shù)情況下鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖,減少不必要的 CAS 操作。
偏向鎖,顧名思義,它會(huì)偏向于第一個(gè)訪問(wèn)鎖的線程,如果在運(yùn)行過(guò)程中, 同步鎖只有一個(gè)線程訪問(wèn),不存在多線程爭(zhēng)用的情況,則線程是不需要觸發(fā)同步的,減少加鎖/解鎖的一些 CAS 操作(比如等待隊(duì)列的一些 CAS 操作),這種情況下,就會(huì)給線程加一個(gè)偏向鎖。 如果在運(yùn)行過(guò)程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會(huì)被掛起,JVM 會(huì)消除它身上的偏向鎖,將鎖恢復(fù)到標(biāo) 準(zhǔn)的輕量級(jí)鎖。它通過(guò)消除資源無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ),進(jìn)一步提高了程序的 運(yùn)行性能。
看下面圖,了解偏向鎖獲取過(guò)程:
步驟 1、 訪問(wèn) Mark Word 中偏向鎖的標(biāo)識(shí)是否設(shè)置成 1,鎖標(biāo)志位是否為 01,確認(rèn)為可偏向狀態(tài)。
步驟 2、 如果為可偏向狀態(tài),則測(cè)試線程 ID 是否指向當(dāng)前線程,如果是, 進(jìn)入步驟 5,否則進(jìn)入步驟 3。
步驟 3、 如果線程 ID 并未指向當(dāng)前線程,則通過(guò) CAS 操作競(jìng)爭(zhēng)鎖。如果競(jìng) 爭(zhēng)成功,則將 Mark Word 中線程 ID 設(shè)置為當(dāng)前線程 ID,然后執(zhí)行 5;如果競(jìng)爭(zhēng) 失敗,執(zhí)行 4。
步驟 4、 如果 CAS 獲取偏向鎖失敗,則表示有競(jìng)爭(zhēng)。當(dāng)?shù)竭_(dá)全局安全點(diǎn) (safepoint)時(shí)獲得偏向鎖的線程被掛起,偏向鎖升級(jí)為輕量級(jí)鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼。(撤銷偏向鎖的時(shí)候會(huì)導(dǎo)致 stop the word)
步驟 5、 執(zhí)行同步代碼。
偏向鎖的釋放:
偏向鎖的撤銷在上述第四步驟中有提到。偏向鎖只有遇到其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放偏向鎖,線程不會(huì)主動(dòng)去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒(méi)有字節(jié)碼正在執(zhí)行),它會(huì)首先暫停擁有偏向鎖的線程,判斷鎖對(duì)象是否處于被鎖定狀態(tài),撤銷偏向鎖后恢復(fù)到未鎖定(標(biāo)志位為“01”)或輕量級(jí)鎖(標(biāo)志位為“00”)的狀態(tài)。
偏向鎖的適用場(chǎng)景:
始終只有一個(gè)線程在執(zhí)行同步塊,在它沒(méi)有執(zhí)行完釋放鎖之前,沒(méi)有其它線程去執(zhí)行同步塊,在鎖無(wú)競(jìng)爭(zhēng)的情況下使用,一旦有了競(jìng)爭(zhēng)就升級(jí)為輕量級(jí)鎖,升級(jí)為輕量級(jí)鎖的時(shí)候需要撤銷偏向鎖,撤銷偏向鎖的時(shí)候會(huì)導(dǎo)致 stop the word 操作;
在有鎖的競(jìng)爭(zhēng)時(shí),偏向鎖會(huì)多做很多額外操作,尤其是撤銷偏向鎖的時(shí)候會(huì)導(dǎo)致進(jìn)入安全點(diǎn),安全點(diǎn)會(huì)導(dǎo)致 stw,導(dǎo)致性能下降,這種情況下應(yīng)當(dāng)禁用。
jvm 開啟/關(guān)閉偏向鎖
開啟偏向鎖:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 關(guān)閉偏向鎖:-XX:-UseBiasedLocking
4.3、 輕量級(jí)鎖
輕量級(jí)鎖是由偏向鎖升級(jí)來(lái)的,偏向鎖運(yùn)行在一個(gè)線程進(jìn)入同步塊的情況下,當(dāng)?shù)诙€(gè)線程加入鎖爭(zhēng)用的時(shí)候,偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖;
輕量級(jí)鎖的加鎖過(guò)程:
- 在代碼進(jìn)入同步塊的時(shí)候,如果同步對(duì)象鎖狀態(tài)為無(wú)鎖狀態(tài)且不允許進(jìn)行偏向(鎖標(biāo)志位為“01”狀態(tài),是否為偏向鎖為“0”),虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的 Mark Word 的拷貝,官方稱之為 Displaced Mark Word。
- 拷貝對(duì)象頭中的 Mark Word 復(fù)制到鎖記錄中。
- 拷貝成功后,虛擬機(jī)將使用 CAS 操作嘗試將對(duì)象的 Mark Word 更新為指向 Lock Record 的指針,并將 Lock record 里的 owner 指針指向 object mark word。如果更新成功,則執(zhí)行步驟 4,否則執(zhí)行步驟 5。
- 如果這個(gè)更新動(dòng)作成功了,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且對(duì)象 Mark Word 的鎖標(biāo)志位設(shè)置為“00”,即表示此對(duì)象處于輕量級(jí)鎖定狀態(tài)
- 如果這個(gè)更新操作失敗了,虛擬機(jī)首先會(huì)檢查對(duì)象的 Mark Word 是否指向當(dāng)前線程的棧幀,如果是就說(shuō)明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行。否則說(shuō)明多個(gè)線程競(jìng)爭(zhēng)鎖,那么它就會(huì)自旋等待鎖,一定次數(shù)后仍未獲得鎖對(duì)象。重量級(jí)線程指針指向競(jìng)爭(zhēng)線程,競(jìng)爭(zhēng)線程也會(huì)阻塞,等待輕量級(jí)線程釋放鎖后喚醒他。鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”,Mark Word 中存儲(chǔ) 的就是指向重量級(jí)鎖(互斥量)的指針,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)。
4.3.1、自旋鎖原理
自旋鎖原理非常簡(jiǎn)單,如果持有鎖的線程能在很短時(shí)間內(nèi)釋放鎖資源,那么那些等待競(jìng)爭(zhēng)鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞掛起狀態(tài),它們只需要等一等(自旋),等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內(nèi)核的切換的消耗。
但是線程自旋是需要消耗 CPU 的,說(shuō)白了就是讓 CPU 在做無(wú)用功,線程不能一直占用 CPU 自旋做無(wú)用功,所以需要設(shè)定一個(gè)自旋等待的最大時(shí)間。
如果持有鎖的線程執(zhí)行的時(shí)間超過(guò)自旋等待的最大時(shí)間扔沒(méi)有釋放鎖,就會(huì)導(dǎo)致其它爭(zhēng)用鎖的線程在最大等待時(shí)間內(nèi)還是獲取不到鎖,這時(shí)爭(zhēng)用線程會(huì)停止自旋進(jìn)入阻塞狀態(tài)。
4.3.2、自旋鎖的優(yōu)缺點(diǎn)
自旋鎖盡可能的減少線程的阻塞,這對(duì)于鎖的競(jìng)爭(zhēng)不激烈,且占用鎖時(shí)間非常短的代碼塊來(lái)說(shuō)性能能大幅度的提升,因?yàn)樽孕南臅?huì)小于線程阻塞掛起操作的消耗。
但是如果鎖的競(jìng)爭(zhēng)激烈,或者持有鎖的線程需要長(zhǎng)時(shí)間占用鎖執(zhí)行同步塊,這時(shí)候就不適合使用自旋鎖了,因?yàn)樽孕i在獲取鎖前一直都是占用 cpu 做無(wú)用 功,占著 茅坑 不 那啥,線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要 cup 的線程又不能獲取到 cpu,造成 cpu 的浪費(fèi)。
4.3.3、自旋鎖時(shí)間閾值
自旋鎖的目的是為了占著 CPU 的資源不釋放,等到獲取到鎖立即進(jìn)行處理。 但是如何去選擇自旋的執(zhí)行時(shí)間呢?如果自旋執(zhí)行時(shí)間太長(zhǎng),會(huì)有大量的線程處于自旋狀態(tài)占用 CPU 資源,進(jìn)而會(huì)影響整體系統(tǒng)的性能。因此自旋次數(shù)很重要。
JVM 對(duì)于自旋次數(shù)的選擇,jdk1.5 默認(rèn)為 10 次,在 1.6 引入了適應(yīng)性自旋鎖, 適應(yīng)性自旋鎖意味著自旋的時(shí)間不在是固定的了,而是由前一次在同一個(gè)鎖上的 自旋時(shí)間以及鎖的擁有者的狀態(tài)來(lái)決定,基本認(rèn)為一個(gè)線程上下文切換的時(shí)間是 最佳的一個(gè)時(shí)間。
JDK1.6 中-XX:+UseSpinning 開啟自旋鎖; JDK1.7 后,去掉此參數(shù),由 jvm 控 制;
4.3.4、不同鎖的比較
推薦學(xué)習(xí):《java視頻教程》