欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長(zhǎng)資訊網(wǎng)
          最全最豐富的資訊網(wǎng)站

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          本篇文章給大家?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ì)大家有幫助。

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          推薦學(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)告》我們可以看到

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          計(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)存讀寫了。

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          在計(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ù)的子集。

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          在現(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)化。

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          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ì)象的鎖:

          1. 如果 monitor 的進(jìn)入數(shù)為 0,則該線程進(jìn)入 monitor,然后將進(jìn)入數(shù)設(shè)置為 1,該線程即為 monitor 的所有者。
          2. 如果線程已經(jīng)占有該 monitor,只是重新進(jìn)入,則進(jìn)入 monitor 的進(jìn)入數(shù)加 1。
          3. 如果其他線程已經(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 兩部分組成:

          1. mark word 存儲(chǔ)了同步狀態(tài)、標(biāo)識(shí)、hashcode、GC 狀態(tài)等等。
          2. klass pointer 存儲(chǔ)對(duì)象的類型指針,該指針指向它的類元數(shù)據(jù) 另外對(duì)于數(shù)組而言還會(huì)有一份記錄數(shù)組長(zhǎng)度的數(shù)據(jù)。

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          鎖信息則是存在于對(duì)象的 mark word 中,MarkWord 里默認(rèn)數(shù)據(jù)是存儲(chǔ)對(duì)象的 HashCode 等信息。

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          但是會(huì)隨著對(duì)象的運(yùn)行改變而發(fā)生變化,不同的鎖狀態(tài)對(duì)應(yīng)著不同的記錄存儲(chǔ)方式

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          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ò)程:

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          步驟 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ò)程:

          1. 在代碼進(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。
          2. 拷貝對(duì)象頭中的 Mark Word 復(fù)制到鎖記錄中。
          3. 拷貝成功后,虛擬機(jī)將使用 CAS 操作嘗試將對(duì)象的 Mark Word 更新為指向 Lock Record 的指針,并將 Lock record 里的 owner 指針指向 object mark word。如果更新成功,則執(zhí)行步驟 4,否則執(zhí)行步驟 5。
          4. 如果這個(gè)更新動(dòng)作成功了,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且對(duì)象 Mark Word 的鎖標(biāo)志位設(shè)置為“00”,即表示此對(duì)象處于輕量級(jí)鎖定狀態(tài)
          5. 如果這個(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 控 制;

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          4.3.4、不同鎖的比較

          Java線程學(xué)習(xí)之并發(fā)編程知識(shí)點(diǎn)

          推薦學(xué)習(xí):《java視頻教程》

          贊(0)
          分享到: 更多 (0)
          網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)