在開(kāi)始這篇blog之前應(yīng)該先了解幾個(gè)概念:
臨界區(qū): 臨界區(qū)指的是一個(gè)訪問(wèn)共用資源(例如:共用設(shè)備或是共用存儲(chǔ)器)的程序片段,而這些共用資源又無(wú)法同時(shí)被多個(gè)線程訪問(wèn)的特性。當(dāng)有線程進(jìn)入臨界區(qū)段時(shí),其他線程或是進(jìn)程必須等待(例如:bounded waiting 等待法),有一些同步的機(jī)制必須在臨界區(qū)段的進(jìn)入點(diǎn)與離開(kāi)點(diǎn)實(shí)現(xiàn),以確保這些共用資源是被互斥獲得使用,例如:semaphore。只能被單一線程訪問(wèn)的設(shè)備,例如:打印機(jī)。 互斥量: 互斥量是一個(gè)可以處于兩態(tài)之一的變量:解鎖和加鎖。這樣,只需要一個(gè)二進(jìn)制位表示它,不過(guò)實(shí)際上,常常使用一個(gè)整型量,0表示解鎖,而其他所有的值則表示加鎖?;コ饬渴褂脙蓚€(gè)過(guò)程。當(dāng)一個(gè)線程(或進(jìn)程)需要訪問(wèn)臨界區(qū)時(shí),它調(diào)用mutex_lock。如果該互斥量當(dāng)前是解鎖的(即臨界區(qū)可用),此調(diào)用成功,調(diào)用線程可以自由進(jìn)入該臨界區(qū)。 另一方面,如果該互斥量已經(jīng)加鎖,調(diào)用線程被阻塞,直到在臨界區(qū)中的線程完成并調(diào)用mutex_unlock。如果多個(gè)線程被阻塞在該互斥量上,將隨機(jī)選擇一個(gè)線程并允許它獲得鎖。 管程: 管程 (英語(yǔ):Monitors,也稱為監(jiān)視器) 是一種程序結(jié)構(gòu),結(jié)構(gòu)內(nèi)的多個(gè)子程序(對(duì)象或模塊)形成的多個(gè)工作線程互斥訪問(wèn)共享資源。這些共享資源一般是硬件設(shè)備或一群變數(shù)。 管程實(shí)現(xiàn)了在一個(gè)時(shí)間點(diǎn),最多只有一個(gè)線程在執(zhí)行管程的某個(gè)子程序。與那些通過(guò)修改數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)互斥訪問(wèn)的并發(fā)程序設(shè)計(jì)相比,管程實(shí)現(xiàn)很大程度上簡(jiǎn)化了程序設(shè)計(jì) 系統(tǒng)中的各種硬件資源和軟件資源,均可用數(shù)據(jù)結(jié)構(gòu)抽象地描述其資源特性,即用少量信息和對(duì)資源所執(zhí)行的操作來(lái)表征該資源,而忽略了它們的內(nèi)部結(jié)構(gòu)和實(shí)現(xiàn)細(xì)節(jié)。 利用共享數(shù)據(jù)結(jié)構(gòu)抽象地表示系統(tǒng)中的共享資源,而把對(duì)該共享數(shù)據(jù)結(jié)構(gòu)實(shí)施的操作定義為一組過(guò)程。 信號(hào)量: 信號(hào)量(Semaphore),有時(shí)被稱為信號(hào)燈,是在多線程環(huán)境下使用的一種設(shè)施,是可以用來(lái)保證兩個(gè)或多個(gè)關(guān)鍵代碼段不被并發(fā)調(diào)用。在進(jìn)入一個(gè)關(guān)鍵代碼段之前,線程必須獲取一個(gè)信號(hào)量;一旦該關(guān)鍵代碼段完成了,那么該線程必須釋放信號(hào)量。其它想進(jìn)入該關(guān)鍵代碼段的線程必須等待直到第一個(gè)線程釋放信號(hào)量。為了完成這個(gè)過(guò)程,需要?jiǎng)?chuàng)建一個(gè)信號(hào)量VI,然后將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個(gè)關(guān)鍵代碼段的首末端。確認(rèn)這些信號(hào)量VI引用的是初始創(chuàng)建的信號(hào)量。 CAS操作(Compare-and-Swap): CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。更詳細(xì)資料:http://zl198751./blog/1848575 重排序: 編譯器和處理器”為了提高性能,而在程序執(zhí)行時(shí)會(huì)對(duì)程序進(jìn)行的重排序。它的出現(xiàn)是為了提高程序的并發(fā)度,從而提高性能!但是對(duì)于多線程程序,重排序可能會(huì)導(dǎo)致程序執(zhí)行的結(jié)果不是我們需要的結(jié)果!重排序分為“編譯器”和“處理器”兩個(gè)方面,而“處理器”重排序又包括“指令級(jí)重排序”和“內(nèi)存的重排序”。 一、線程與內(nèi)存交互操作 ![]() 所有的變量(實(shí)例字段,靜態(tài)字段,構(gòu)成數(shù)組對(duì)象的 元素,不包括局部變量和方法參數(shù))都存儲(chǔ)在主內(nèi)存中,每個(gè)線程有自己的工作內(nèi)存,線程的工作內(nèi)存保存被線程使用到變量的主內(nèi)存副本拷貝。線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存的變量。不同線程之間也不能直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞通過(guò)主內(nèi)存來(lái)完成。 Java內(nèi)存模型定義了八種操作:
volatile關(guān)鍵字作用: 1)保證了新值能立即存儲(chǔ)到主內(nèi)存,每次使用前立即從主內(nèi)存中刷新。 2)禁止指令重排序優(yōu)化。 注:volatile關(guān)鍵字不能保證在多線程環(huán)境下對(duì)共享數(shù)據(jù)的操作的正確性??梢允褂迷谧约籂顟B(tài)改變之后需要立即通知所有線程的情況下。 二、并發(fā)的三個(gè)特性 原子性 原子性是指不可再分的最小操作指令,即單條機(jī)器指令,原子性操作任意時(shí)刻只能有一個(gè)線程,因此是線程安全的。 Java內(nèi)存模型中通過(guò)read、load、assign、use、store和write這6個(gè)操作保證變量的原子性操作。 long和double這兩個(gè)64位長(zhǎng)度的數(shù)據(jù)類型java虛擬機(jī)并沒(méi)有強(qiáng)制規(guī)定他們的read、load、store和write操作的原子性,即所謂的非原子性協(xié)定,但是目前的各種商業(yè)java虛擬機(jī)都把long和double數(shù)據(jù)類型的4中非原子性協(xié)定操作實(shí)現(xiàn)為原子性。所以java中基本數(shù)據(jù)類型的訪問(wèn)讀寫(xiě)是原子性操作。 對(duì)于大范圍的原子性保證需要通過(guò)lock和unlock操作以及synchronized同步塊來(lái)保證。 可見(jiàn)性 可見(jiàn)性是指當(dāng)一個(gè)線程修改了共享變量的值,其他線程可以立即得知這個(gè)修改。 Java內(nèi)存模型是通過(guò)在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值來(lái)實(shí)現(xiàn)可見(jiàn)性的。 Java中通過(guò)volatile、final和synchronized這三個(gè)關(guān)鍵字保證可見(jiàn)性:
有序性 線程的有序性是指:在線程內(nèi)部,所有的操作都是有序執(zhí)行的,而在線程之間,因?yàn)楣ぷ鲀?nèi)存和主內(nèi)存同步的延遲,操作是亂序執(zhí)行的。 Java通過(guò)volatile和synchronized關(guān)鍵字確保線程之間操作的有序性。
三、java線程的實(shí)現(xiàn)方式 線程實(shí)現(xiàn)的三種方式: 內(nèi)核線程(Kernal thread) 內(nèi)核線程(Kernel Thread, KLT)就是直接由操作系統(tǒng)內(nèi)核支持的線程,這種線程由內(nèi)核來(lái)完成線程切換,內(nèi)核通過(guò)操作調(diào)度器對(duì)線程進(jìn)行調(diào)度,并負(fù)責(zé)將線程的任務(wù)映射到各個(gè)處理器上。程序一般不會(huì)直接去使用內(nèi)核線程,而是去使用內(nèi)核線程的一種高級(jí)接口——輕量級(jí)進(jìn)程(Light Weight Process,LWP),輕量級(jí)進(jìn)程就是我們通常意義上所講的線程,由于每個(gè)輕量級(jí)進(jìn)程都由一個(gè)內(nèi)核線程支持,因此只有先支持內(nèi)核線程,才能有輕量級(jí)進(jìn)程。這種輕量級(jí)進(jìn)程與內(nèi)核線程之間1:1的關(guān)系稱為一對(duì)一的線程模型。輕量級(jí)進(jìn)程要消耗一定的內(nèi)核資源(如內(nèi)核線程的??臻g),而且系統(tǒng)調(diào)用的代價(jià)相對(duì)較高,因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程的數(shù)量是有限的。 輕量級(jí)用戶進(jìn)程(Light weight process) 廣義上來(lái)講,一個(gè)線程只要不是內(nèi)核線程,那就可以認(rèn)為是用戶線程(User Thread,UT),而狹義的用戶線程指的是完全建立在用戶空間的線程庫(kù)上,系統(tǒng)內(nèi)核不能感知到線程存在的實(shí)現(xiàn),用戶線程的建立、同步、銷毀和調(diào)度完全在用戶態(tài)中完成,不需要內(nèi)核的幫助。如果程序?qū)崿F(xiàn)得當(dāng),這種線程不需要切換到內(nèi)核態(tài),因此操作可以是非??焖偾业拖牡?,也可以支持規(guī)模更大的線程數(shù)量,部分高性能數(shù)據(jù)庫(kù)中的多線程就是由用戶線程實(shí)現(xiàn)的。這種進(jìn)程與用戶線程之間1:N的關(guān)系稱為一對(duì)多的線程模型。(Windows和Linux使用的是這種方式) 使用用戶線程的優(yōu)勢(shì)在于不需要系統(tǒng)內(nèi)核的支援,劣勢(shì)在于沒(méi)有系統(tǒng)內(nèi)核的支援,所有的線程操作都需要用戶程序自己處理,因而使用用戶線程實(shí)現(xiàn)的程序一般都比較復(fù)雜,現(xiàn)在使用用戶線程的程序越來(lái)越少了。 用戶線程/混合線程(User thread) 既存在用戶線程,又存在輕量級(jí)進(jìn)程。用戶線程還是完全建立在用戶空間中,而操作系統(tǒng)所支持的輕量級(jí)進(jìn)程則作為用戶線程和內(nèi)核線程之間的橋梁。這種混合模式下,用戶線程與輕量級(jí)進(jìn)程的數(shù)量比是不定的,是M:N的關(guān)系。許多Unix系列的系統(tǒng),都提供了M:N的線程模型實(shí)現(xiàn)。 java線程調(diào)度 Java線程在JDK1.2之前,是基于名為“綠色線程”的用戶線程實(shí)現(xiàn)的,而在JDK1.2中,線程模型被替換為基于操作系統(tǒng)原生線程模型來(lái)實(shí)現(xiàn)。因此,在目前的JDK版本中,操作系統(tǒng)支持怎樣的線程模型,在很大程度上就決定了Java虛擬機(jī)的線程是怎樣映射的,這點(diǎn)在不同的平臺(tái)上沒(méi)有辦法達(dá)成一致,虛擬機(jī)規(guī)范中也未限定Java線程需要使用哪種線程模型來(lái)實(shí)現(xiàn)。 線程調(diào)度有兩種方式 協(xié)同式:線程的執(zhí)行時(shí)間由線程本身來(lái)控制,線程任務(wù)執(zhí)行完成之后主動(dòng)通知系統(tǒng)切換到另一個(gè)線程去執(zhí)行。(不推薦) 優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,線程切換操作對(duì)線程本身是可知的,不存在線程同步問(wèn)題。 缺點(diǎn):線程執(zhí)行時(shí)間不可控制,如果線程長(zhǎng)時(shí)間執(zhí)行不讓出CPU執(zhí)行時(shí)間可能導(dǎo)致系統(tǒng)崩潰。 搶占式:每個(gè)線程的執(zhí)行時(shí)間有操作系統(tǒng)來(lái)分配,操作系統(tǒng)給每個(gè)線程分配執(zhí)行的時(shí)間片,搶到時(shí)間片的線程執(zhí)行,時(shí)間片用完之后重新?lián)屨紙?zhí)行時(shí)間,線程的切換不由線程本身來(lái)決定(Java使用的線程調(diào)度方式就是搶占式調(diào)度)。 優(yōu)點(diǎn):線程執(zhí)行時(shí)間可控制,不會(huì)因?yàn)橐粋€(gè)線程阻塞問(wèn)題導(dǎo)致系統(tǒng)崩潰。 四、Java中線程狀態(tài)的調(diào)度關(guān)系 ![]() 五、java中的線程安全等級(jí) 不可變: 可以是基本類型的final;可以是final對(duì)象,但對(duì)象的行為不會(huì)對(duì)其狀態(tài)產(chǎn)生任何影響,比如String的subString就是new一個(gè)String對(duì)象各種Number類型如BigInteger和BigDecimal等大數(shù)據(jù)類型都是不可變的,但是同為Number子類型的AtomicInteger和AtomicLong則并非不可變。原因與它里面狀態(tài)對(duì)象是unsafe對(duì)象有關(guān),所做的操作都是CAS操作,可以保證原子性。 絕對(duì)線程安全: 不管運(yùn)行時(shí)環(huán)境如何,調(diào)用者都不需要任何額外的同步措施。 相對(duì)線程安全: 這是我們通常意義上的線程安全。需要保證對(duì)象單獨(dú)的操作是線程安全的。比如Vector,HashTable,synchronizedCollection包裝集合等。 線程兼容: 對(duì)象本身不是線程安全的,但可以通過(guò)同步手段實(shí)現(xiàn)。一般我們說(shuō)的不是線程安全的,絕大多數(shù)是指這個(gè)。比如ArrayList,HashMap等。 線程對(duì)立: 不管調(diào)用端是否采用了同步的措施,都無(wú)法在并發(fā)中使用的代碼。 六、線程安全的實(shí)現(xiàn)方式 互斥同步 在多線程訪問(wèn)的時(shí)候,保證同一時(shí)間只有一條線程使用。 臨界區(qū)(Critical Section),互斥量(Mutex),信號(hào)量(Semaphore)都是同步的一種手段 java里最基本的互斥同步手段是synchronized,編譯之后會(huì)形成monitorenter和monitorexit這兩個(gè)字節(jié)碼指令,這兩個(gè)字節(jié)碼都需要一個(gè)reference類型的參數(shù)來(lái)指明要鎖定和解鎖的對(duì)象,還有一個(gè)鎖的計(jì)數(shù)器,來(lái)記錄加鎖的次數(shù),加鎖幾次就要同樣解鎖幾次才能恢復(fù)到無(wú)鎖狀態(tài)。 其實(shí)在“Java與線程”里已經(jīng)提到,java的線程是映射到操作系統(tǒng)的原生線程之上的,不管阻塞還是喚醒都需要操作系統(tǒng)的幫忙完成,都需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這是很耗費(fèi)時(shí)間的,是java語(yǔ)言中的一個(gè)重量級(jí)(Heavyweight)操作,雖然虛擬機(jī)本身會(huì)做一點(diǎn)優(yōu)化的操作,比如通知操作系統(tǒng)阻塞之前會(huì)加一段自旋等待的過(guò)程,避免頻繁切換到核心態(tài)。 ReentrantLock相比于synchronized的優(yōu)勢(shì):
非阻塞同步 互斥和同步最主要的問(wèn)題就是阻塞和喚醒所帶來(lái)的性能問(wèn)題,所以這通常叫阻塞同步(悲觀的并發(fā)策略)。隨著硬件指令集的發(fā)展,我們有另外的選擇:基于沖突檢測(cè)的樂(lè)觀并發(fā)策略,通俗講就是先操作,如果沒(méi)有其他線程爭(zhēng)用共享的數(shù)據(jù),操作就成功,如果有,則進(jìn)行其他的補(bǔ)償(最常見(jiàn)就是不斷的重試),這種樂(lè)觀的并發(fā)策略許多實(shí)現(xiàn)都不需要把線程掛起,這種同步操作被稱為非阻塞同步。 這類的指令有: 1)測(cè)試并設(shè)置(test-and-set) 2)獲取并增加 3)交換 4)比較并交換(CAS) 5)加載鏈接/條件儲(chǔ)存(Load-Linked/Store-Conditional LL/SC) 后面兩條是現(xiàn)代處理器新增的處理器指令,在JDK1.5之后,java中才可以使用CAS操作,就是傳說(shuō)中的sun.misc.Unsafe類里面的compareAndSwapInt()和compareAndSwapLong()等幾個(gè)方法的包裝提供,虛擬機(jī)對(duì)這些方法做了特殊的處理,及時(shí)編譯出來(lái)的結(jié)果就是一條平臺(tái)相關(guān)的處理器CAS指令,沒(méi)有方法調(diào)用的過(guò)程,可以認(rèn)為是無(wú)條件的內(nèi)聯(lián)進(jìn)去。 原來(lái)需要對(duì)i++進(jìn)行同步,但現(xiàn)在有了這種CAS操作來(lái)保證原子性,比如用AtomicInteger。 但是CAS存在一個(gè)ABA的問(wèn)題??梢酝ㄟ^(guò)AtomicStampedReference來(lái)解決(雞肋)。 無(wú)同步 有一些代碼天生就是線程安全的,不需要同步。其中有如下兩類: 可重入代碼(Reentrant Code):純代碼,具有不依賴存儲(chǔ)在堆上的數(shù)據(jù)和公用的系統(tǒng)資源,用到的狀態(tài)量都由參數(shù)中傳入,不調(diào)用非可重入的方法等特征,它的返回結(jié)果是可以預(yù)測(cè)的。 線程本地存儲(chǔ)(Thread Local Storage):把共享數(shù)據(jù)的可見(jiàn)范圍限制在同一個(gè)線程之內(nèi),這樣就無(wú)須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭(zhēng)用問(wèn)題。可以通過(guò)java.lang.ThreadLocal類來(lái)實(shí)現(xiàn)線程本地存儲(chǔ)的功能。 七、java中的鎖機(jī)制 悲觀鎖 假定會(huì)發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作。悲觀鎖假定其他線程企圖訪問(wèn)或者改變你正在訪問(wèn)、更改的對(duì)象的概率是很高的,因此在悲觀鎖的環(huán)境中,在你開(kāi)始改變此對(duì)象之前就將該對(duì)象鎖住,并且直到你提交了所作的更改之后才釋放鎖。 樂(lè)觀鎖 假設(shè)不會(huì)發(fā)生并發(fā)沖突。輕易不加鎖。 自旋鎖與自適應(yīng)自旋 線程掛起和恢復(fù)的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性能帶來(lái)了很大的壓力,在許多應(yīng)用中,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間,為了這段時(shí)間去掛起和恢復(fù)線程并不值得,可以讓后請(qǐng)求鎖的線程等待一會(huì)兒,但不放棄處理器的執(zhí)行時(shí)間,讓線程執(zhí)行一個(gè)忙循環(huán)(自旋)。 自旋鎖默認(rèn)的自旋次數(shù)值是10次,可以使用參數(shù)-XX:PreBlockSpin更改。 自適應(yīng)自旋意味著自旋的時(shí)間不再固定,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定。 鎖清除: 虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除。鎖消除的主要判定依據(jù)來(lái)源于逃逸分析的數(shù)據(jù)支持。 鎖粗化: 如果虛擬機(jī)探測(cè)到有一系列連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部。 鎖升級(jí) Java SE1.6為了減少獲得鎖和釋放鎖所帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”,所以在Java SE1.6里鎖一共有四種狀態(tài),無(wú)鎖狀態(tài),偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí),意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖。這種鎖升級(jí)卻不能降級(jí)的策略,目的是為了提高獲得鎖和釋放鎖的效率。 ![]() 偏向鎖 Hotspot的作者經(jīng)過(guò)以往的研究發(fā)現(xiàn)大多數(shù)情況下鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。當(dāng)一個(gè)線程訪問(wèn)同步塊并獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄里存儲(chǔ)鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要花費(fèi)CAS操作來(lái)加鎖和解鎖,而只需簡(jiǎn)單的測(cè)試一下對(duì)象頭的Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖,如果測(cè)試成功,表示線程已經(jīng)獲得了鎖,如果測(cè)試失敗,則需要再測(cè)試下Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1(表示當(dāng)前是偏向鎖),如果沒(méi)有設(shè)置,則使用CAS競(jìng)爭(zhēng)鎖,如果設(shè)置了,則嘗試使用CAS將對(duì)象頭的偏向鎖指向當(dāng)前線程。 偏向鎖的撤銷:偏向鎖使用了一種等到競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒(méi)有字節(jié)碼正在執(zhí)行),它會(huì)首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動(dòng)狀態(tài),則將對(duì)象頭設(shè)置成無(wú)鎖狀態(tài),如果線程仍然活著,擁有偏向鎖的棧會(huì)被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄,棧中的鎖記錄和對(duì)象頭的Mark Word要么重新偏向于其他線程,要么恢復(fù)到無(wú)鎖或者標(biāo)記對(duì)象不適合作為偏向鎖,最后喚醒暫停的線程。下圖中的線程1演示了偏向鎖初始化的流程,線程2演示了偏向鎖撤銷的流程。 ![]() 關(guān)閉偏向鎖:偏向鎖在Java 6和Java 7里是默認(rèn)啟用的,但是它在應(yīng)用程序啟動(dòng)幾秒鐘之后才激活,如有必要可以使用JVM參數(shù)來(lái)關(guān)閉延遲-XX:BiasedLockingStartupDelay = 0。如果你確定自己應(yīng)用程序里所有的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài),可以通過(guò)JVM參數(shù)關(guān)閉偏向鎖-XX:-UseBiasedLocking=false,那么默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。 輕量級(jí)鎖: 輕量級(jí)鎖加鎖:線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競(jìng)爭(zhēng)鎖,當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖。 輕量級(jí)鎖解鎖:輕量級(jí)解鎖時(shí),會(huì)使用原子的CAS操作來(lái)將Displaced Mark Word替換回到對(duì)象頭,如果成功,則表示沒(méi)有競(jìng)爭(zhēng)發(fā)生。如果失敗,表示當(dāng)前鎖存在競(jìng)爭(zhēng),鎖就會(huì)膨脹成重量級(jí)鎖。下圖是兩個(gè)線程同時(shí)爭(zhēng)奪鎖,導(dǎo)致鎖膨脹的流程圖。 ![]() 因?yàn)樽孕龝?huì)消耗CPU,為了避免無(wú)用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級(jí)成重量級(jí)鎖,就不會(huì)再恢復(fù)到輕量級(jí)鎖狀態(tài)。當(dāng)鎖處于這個(gè)狀態(tài)下,其他線程試圖獲取鎖時(shí),都會(huì)被阻塞住,當(dāng)持有鎖的線程釋放鎖之后會(huì)喚醒這些線程,被喚醒的線程就會(huì)進(jìn)行新一輪的奪鎖之爭(zhēng)。 重量級(jí)鎖: 重量鎖在JVM中又叫對(duì)象監(jiān)視器(Monitor),它至少包含一個(gè)競(jìng)爭(zhēng)鎖的隊(duì)列,和一個(gè)信號(hào)阻塞隊(duì)列(wait隊(duì)列),前者負(fù)責(zé)做互斥,后一個(gè)用于做線程同步。 |
|
來(lái)自: liang1234_ > 《鎖機(jī)制》