[TOC]
13.2 線程安全
- 《Java Concurrency In Practice》對(duì) “線程安全” 有一個(gè)比較恰當(dāng)?shù)亩x:“當(dāng)多個(gè)線程訪問一個(gè)對(duì)象時(shí)废岂,如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行捷绒,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作凉唐,調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果溺森,那這個(gè)對(duì)象是線程安全的”癞己。
13.2.1 Java 語言中的線程安全
- 按照線程安全的 “安全程度” 由強(qiáng)至弱來排序,我們可以將 Java 語言中各種操作共享的數(shù)據(jù)分為以下5類:
- 不可變
- 絕對(duì)線程安全
- 相對(duì)線程安全
- 線程兼容
- 線程對(duì)立
13.2.1.1 不可變
- 在 Java 語言中(特指 JDK1.5 以后似将,即 Java 內(nèi)存模型被修正之后的 Java 語言)获黔,不可變(Immutable)的對(duì)象一定是線程安全的,無論是對(duì)象的方法實(shí)現(xiàn)還是方法的調(diào)用者在验,都不需要再采取任何的線程安全保障措施玷氏,“不可變” 帶來的安全性是最簡(jiǎn)單和最純粹的。
- 如果共享數(shù)據(jù)是一個(gè)基本數(shù)據(jù)類型腋舌,那么只要在定義時(shí)使用 final 關(guān)鍵字修飾它就可以保證它是不可變的盏触。
- 如果共享數(shù)據(jù)是一個(gè)對(duì)象,那就需要保證對(duì)象的行為不會(huì)對(duì)其狀態(tài)產(chǎn)生任何影響才行块饺,其中最簡(jiǎn)單的就是把對(duì)象中帶有狀態(tài)的變量都聲明為 final赞辩,這樣在構(gòu)造函數(shù)結(jié)束之后,它就是不可變的授艰,例如 Integer 類
13.2.1.2 絕對(duì)線程安全
絕對(duì)的線程安全完全滿足上述給出的線程安全的定義辨嗽,這個(gè)定義其實(shí)是很嚴(yán)格的,一個(gè)類要達(dá)到 “不管運(yùn)行時(shí)環(huán)境如何淮腾,調(diào)用者都不需要任何額外的同步措施” 通常需要付出很大的糟需,甚至有時(shí)候是不切實(shí)際的代價(jià)。在 JavaAPI 中標(biāo)注自己是線程安全的類谷朝,大多數(shù)都不是絕對(duì)的線程安全洲押。
在如下代碼中,盡管這里使用到的 Vector 的
get()
徘禁、remove()
和size()
方法都是同步的诅诱,但是在多線程的環(huán)境中,如果不在方法調(diào)用端做額外的同步措施的話送朱,使用這段代碼仍然是不安全的娘荡,因?yàn)槿绻硪粋€(gè)線程恰好在錯(cuò)誤的時(shí)間里刪除了一個(gè)元素干旁,導(dǎo)致序號(hào) i 已經(jīng)不再可用的話,再用 i 訪問數(shù)組就會(huì)拋出異常炮沐。
public class VectorTest
{
private static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args)
{
while (true)
{
for (int i = 0; i < 10; i++)
{
vector.add(i);
}
Thread removeThread = new Thread(new Runnable()
{
@Override
public void run()
{
for (int i = 0; i < vector.size(); i++)
{
vector.remove(i);
}
}
});
Thread printThread = new Thread(new Runnable()
{
@Override
public void run()
{
for (int i = 0; i < vector.size(); i++)
{
System.out.println((vector.get(i)));
}
}
});
removeThread.start();
printThread.start();
//不要同時(shí)產(chǎn)生過多的線程争群,否則會(huì)導(dǎo)致操作系統(tǒng)假死
while (Thread.activeCount() > 20);
}
}
}
13.2.1.3 相對(duì)線程安全
- 相對(duì)的線程安全就是我們通常意義上所講的線程安全,它需要保證對(duì)這個(gè)對(duì)象單獨(dú)的操作是線程安全的大年,我們?cè)谡{(diào)用的時(shí)候不需要做額外的保障措施换薄,但是對(duì)于一些特定順序的連續(xù)調(diào)用,就可能需要在調(diào)用端使用額外的同步手段來保證調(diào)用的正確性翔试。
- 在 Java 語言中轻要,大部分的線程安全類都屬于這種類型,例如Vector垦缅、HashTable冲泥、Collections的synchronizedCollection() 方法包裝的集合等。
13.2.1.4 線程兼容
- 線程兼容是指對(duì)象本身并不是線程安全的壁涎,但是可以通過在調(diào)用端正確地使用同步手段來保證對(duì)象在并發(fā)環(huán)境中可以安全地使用凡恍。我們平常說一個(gè)類不是線程安全的,絕大多數(shù)時(shí)候指的是這一種情況怔球。
- JavaAPI 中大部分的類都是屬于線程兼容的嚼酝,如 ArrayList 和 HashMap 等。
13.2.1.5 線程對(duì)立
- 線程對(duì)立是指無論調(diào)用端是否采取了同步措施竟坛,都無法在多線程環(huán)境中并發(fā)使用的代碼闽巩。由于 Java 語言天生就具備多線程特性,線程對(duì)立這種排斥多線程的代碼是很少出現(xiàn)的流码,而且通常都是有害的又官,應(yīng)當(dāng)盡量避免。
- 一個(gè)線程對(duì)立的例子是 Thread 類的
suspend()
和resume()
方法漫试,如果有兩個(gè)線程同時(shí)持有一個(gè)線程對(duì)象六敬,一個(gè)嘗試去中斷線程,另一個(gè)嘗試去恢復(fù)線程驾荣,如果并發(fā)進(jìn)行的話外构,無論調(diào)用時(shí)是否進(jìn)行了同步,目標(biāo)線程都是存在死鎖風(fēng)險(xiǎn)的播掷。 - 如果
suspend()
中斷的線程就是即將要執(zhí)行resume()
的那個(gè)線程审编,那就肯定要產(chǎn)生死鎖了。
13.2.2 線程安全的實(shí)現(xiàn)方法
13.2.2.1 互斥同步
- 互斥同步(Mutual Exclusion & Synchronization)是常見的一種并發(fā)正確性保障手段歧匈。
- 同步是指在多個(gè)線程并發(fā)訪問共享數(shù)據(jù)時(shí)垒酬,保證共享數(shù)據(jù)在同一個(gè)時(shí)刻只被一個(gè)(或者是一些,使用信號(hào)量的時(shí)候)線程使用。
- 互斥是實(shí)現(xiàn)同步的一種手段勘究,臨界區(qū)(Critical Section)矮湘、互斥量(Mutex)和信號(hào)量(Semaphore)都是主要的互斥實(shí)現(xiàn)方式。
- 因此口糕,在這4個(gè)字里面缅阳,互斥是因,同步是果景描;互斥是方法十办,同步是目的。
- 在 Java 中超棺,最基本的互斥同步手段就是 synchronized 關(guān)鍵字:
- synchronized 關(guān)鍵字經(jīng)過編譯之后向族,會(huì)在同步塊的前后分別形成 monitorenter 和 monitorexit 這兩個(gè)字節(jié)碼指令
- 這兩個(gè)字節(jié)碼都需要一個(gè) reference 類型的參數(shù)來指明要鎖定和解鎖的對(duì)象。
- 如果 Java 程序中的 synchronized 明確指定了對(duì)象參數(shù)说搅,那就是這個(gè)對(duì)象的reference(如同步塊)
- 如果沒有明確指定炸枣,那就根據(jù) synchronized 修飾的是實(shí)例方法還是類方法,去取對(duì)應(yīng)的對(duì)象實(shí)例或 Class 對(duì)象來作為鎖對(duì)象弄唧。
- 根據(jù)虛擬機(jī)規(guī)范的要求,在執(zhí)行monitorenter指令時(shí)霍衫,首先要嘗試獲取對(duì)象的鎖:
- 如果這個(gè)對(duì)象沒被鎖定候引,或者當(dāng)前線程已經(jīng)擁有了那個(gè)對(duì)象的鎖(同一線程可重入),把鎖的計(jì)數(shù)器加1
- 相應(yīng)的敦跌,在執(zhí)行 monitorexit 指令時(shí)會(huì)將鎖計(jì)數(shù)器減1
- 當(dāng)計(jì)數(shù)器為 0 時(shí)澄干,鎖就被釋放
- 如果獲取對(duì)象鎖失敗,那當(dāng)前線程就要阻塞等待柠傍,直到對(duì)象鎖被另外一個(gè)線程釋放為止麸俘。
- 在虛擬機(jī)規(guī)范對(duì) monitorenter 和 monitorexit 的行為描述中,有兩點(diǎn)是需要特別注意的:
- 首先惧笛,synchronized 同步塊對(duì)同一條線程來說是可重入的从媚,不會(huì)出現(xiàn)自己把自己鎖死的問題。
- 其次患整,同步塊在已進(jìn)入的線程執(zhí)行完之前拜效,會(huì)阻塞后面其他線程的進(jìn)入。
- 除了 synchronized 之外各谚,我們還可以使用 java.util.concurrent 包中的重入鎖(ReentrantLock)來實(shí)現(xiàn)同步紧憾,在基本用法上,ReentrantLock 與synchronized很相似昌渤,不過赴穗,ReentrantLock 增加了一些高級(jí)功能,主要有以下3項(xiàng):
- 等待可中斷:當(dāng)持有鎖的線程長(zhǎng)期不釋放鎖的時(shí)候,正在等待的線程可以選擇放棄等待
- 可實(shí)現(xiàn)公平鎖:公平鎖是指多個(gè)線程在等待同一個(gè)鎖時(shí)般眉,必須按照申請(qǐng)鎖的時(shí)間順序來依次獲得鎖了赵;而非公平鎖則不保證這一點(diǎn),在鎖被釋放時(shí)煤篙,任何一個(gè)等待鎖的線程都有機(jī)會(huì)獲得鎖斟览。
- 鎖可以綁定多個(gè)條件:鎖綁定多個(gè)條件是指一個(gè) ReentrantLock 對(duì)象可以同時(shí)綁定多個(gè) Condition 對(duì)象,而在synchronized中辑奈,鎖對(duì)象的
wait()
和notify()
或notifyAll()
方法可以實(shí)現(xiàn)一個(gè)隱含的條件苛茂,如果要和多于一個(gè)的條件關(guān)聯(lián)的時(shí)候,就不得不額外地添加一個(gè)鎖
13.2.2.2 非阻塞同步
- 互斥同步最主要的問題就是進(jìn)行線程阻塞和喚醒所帶來的性能問題鸠窗,因此這種同步也稱為阻塞同步(Blocking Synchronization)妓羊。
- 從處理問題的方式上說,互斥同步屬于一種悲觀的并發(fā)策略稍计,總是認(rèn)為只要不去做正確的同步措施(例如加鎖)躁绸,那就肯定會(huì)出現(xiàn)問題。
- 而另外一個(gè)選擇是:基于沖突檢測(cè)的樂觀并發(fā)策略臣嚣,通俗地說净刮,就是先進(jìn)行操作:
- 如果沒有其他線程爭(zhēng)用共享數(shù)據(jù),那操作就成功了硅则;
- 如果共享數(shù)據(jù)有爭(zhēng)用淹父,產(chǎn)生了沖突,那就再采取其他的補(bǔ)償措施(最常見的補(bǔ)償措施就是不斷地重試怎虫,直到成功為止)
- 這種樂觀的并發(fā)策略的許多實(shí)現(xiàn)都不需要把線程掛起暑认,因此這種同步操作稱為非阻塞同步(Non-Blocking Synchronization)。
- 然而在這里我們需要操作和沖突檢測(cè)這兩個(gè)步驟具備原子性大审,如果這里再使用互斥同步來保證就失去意義了蘸际,所以我們只能靠硬件來完成這件事情,硬件保證一個(gè)從語義上看起來需要多次操作的行為只通過一條處理器指令就能完成徒扶,這類指令常用的有:
- 測(cè)試并設(shè)置(Test-and-Set)
- 獲取并增加(Fetch-and-Increment)
- 交換(Swap)
- 比較并交換(Compare-and-Swap粮彤,即CAS)
- 加載鏈接/條件存儲(chǔ)(Load-Linked/Store-Conditional,即 LL/SC)
- 其中 CAS 指令需要有3個(gè)操作數(shù)酷愧,分別是:
- 內(nèi)存位置(在 Java 中可以簡(jiǎn)單理解為變量的內(nèi)存地址驾诈,用 V 表示)
- 舊的預(yù)期值(用A表示)
- 新值(用B表示)
- CAS 指令執(zhí)行時(shí),當(dāng)且僅當(dāng) V 符合舊預(yù)期值 A 時(shí)溶浴,處理器用新值 B 更新 V 的值乍迄,否則它就不執(zhí)行更新。但是無論是否更新了 V 的值士败,都會(huì)返回 V 的舊值闯两,上述的處理過程是一個(gè)原子操作褥伴。
13.2.2.3 無同步方案
- 要保證線程安全,并不是一定就要進(jìn)行同步漾狼,兩者沒有因果關(guān)系重慢。同步只是保證共享數(shù)據(jù)爭(zhēng)用時(shí)的正確性的手段,如果一個(gè)方法本來就不涉及共享數(shù)據(jù)逊躁,那它自然就無須任何同步措施去保證正確性似踱,因此會(huì)有一些代碼天生就是線程安全的:
- 可重入代碼(Reentrant Code):這種代碼也叫做純代碼(Pure Code),可以在代碼執(zhí)行的任何時(shí)刻中斷它稽煤,轉(zhuǎn)而去執(zhí)行另外一段代碼(包括遞歸調(diào)用它本身)核芽,而在控制權(quán)返回后,原來的程序不會(huì)出現(xiàn)任何錯(cuò)誤酵熙。
- 線程本地存儲(chǔ)(Thread Local Storage):如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享轧简,那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個(gè)線程中執(zhí)行?如果能保證匾二,我們就可以把共享數(shù)據(jù)的可見范圍限制在同一個(gè)線程之內(nèi)哮独,這樣,無須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭(zhēng)用的問題察藐。
13.3 鎖優(yōu)化
13.3.1 自旋鎖與自適應(yīng)自旋
- 互斥同步對(duì)性能最大的影響是阻塞的實(shí)現(xiàn)皮璧,掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性能帶來了很大的壓力分飞。
- 然而共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間恶导,為了這段時(shí)間去掛起和恢復(fù)線程并不值得。如果物理機(jī)器能讓兩個(gè)或以上的線程同時(shí)并行執(zhí)行浸须,我們就可以讓后面請(qǐng)求鎖的那個(gè)線程 “稍等一下”,但不放棄處理器的執(zhí)行時(shí)間邦泄,看看持有鎖的線程是否很快就會(huì)釋放鎖删窒。為了讓線程等待,我們只需讓線程執(zhí)行一個(gè)忙循環(huán)(自旋)顺囊,這項(xiàng)技術(shù)就是所謂的自旋鎖肌索。
- 自旋等待本身雖然避免了線程切換的開銷,但它是要占用處理器時(shí)間的特碳。因此诚亚,如果鎖被占用的時(shí)間很短,自旋等待的效果就會(huì)非常好午乓,反之站宗,如果鎖被占用的時(shí)間很長(zhǎng),那么自旋的線程只會(huì)白白消耗處理器資源益愈。
- 在 JDK1.6 中引入了自適應(yīng)的自旋鎖梢灭。自適應(yīng)意味著自旋的時(shí)間不再固定了夷家,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定:
- 如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲得過鎖敏释,并且持有鎖的線程正在運(yùn)行中库快,那么虛擬機(jī)就會(huì)認(rèn)為這次自旋也很有可能再次成功,進(jìn)而它將允許自旋等待持續(xù)相對(duì)更長(zhǎng)的時(shí)間钥顽,比如100個(gè)循環(huán)义屏。
- 另外,如果對(duì)于某個(gè)鎖蜂大,自旋很少成功獲得過闽铐,那在以后要獲取這個(gè)鎖時(shí)將可能省略掉自旋過程,以避免浪費(fèi)處理器資源县爬。
13.3.2 鎖消除
鎖消除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí)阳啥,對(duì)一些代碼上要求同步,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除财喳。
鎖消除的主要判定依據(jù)來源于逃逸分析的數(shù)據(jù)支持察迟,如果判斷在一段代碼中,堆上的所有數(shù)據(jù)都不會(huì)逃逸出去從而被其他線程訪問到耳高,那就可以把它們當(dāng)做棧上數(shù)據(jù)對(duì)待扎瓶,認(rèn)為它們是線程私有的,同步加鎖自然就無須進(jìn)行泌枪。
變量是否逃逸概荷,對(duì)于虛擬機(jī)來說需要使用數(shù)據(jù)流分析來確定,但是程序員自己應(yīng)該是很清楚的碌燕,怎么會(huì)在明知道不存在數(shù)據(jù)爭(zhēng)用的情況下要求同步呢误证?答案是有許多同步措施并不是程序員自己加入的,同步的代碼在 Java 程序中的普遍程度很高修壕。
在如下代碼中愈捅,無論是源碼字面上還是程序語義上都沒有同步:
public String concatString (String s1, String s2, String s3)
{
return sl + s2 + s3;
}
- 但是由于 String 是一個(gè)不可變的類,對(duì)字符串的連接操作總是通過生成新的 String 對(duì)象來進(jìn)行的慈鸠,因此 Javac編譯器會(huì)對(duì) String 連接做自動(dòng)優(yōu)化蓝谨。在 JDK1.5 之前,會(huì)轉(zhuǎn)化為 StringBuffer 對(duì)象的連續(xù)
append()
操作青团,在 JDK1.5 及以后的版本中譬巫,會(huì)轉(zhuǎn)化為 StringBuilder 對(duì)象的連續(xù)append()
操作,代碼如下:
public String concatString (String s1, String s2, string s3)
{
StringBuffer sb = new StringBuffer();
// lock
sb.append(s1);
// unlock
// lock
sb.append(s2);
// unlock
// lock
sb.append(s3);
// unlock
return sb.toString();
}
- 每個(gè)
StringBuffer.append()
方法中都有一個(gè)同步塊督笆,鎖就是 sb 對(duì)象芦昔。虛擬機(jī)觀察變量 sb,很快就會(huì)發(fā)現(xiàn)它的動(dòng)態(tài)作用域被限制在concatString()
方法內(nèi)部胖腾。也就是說烟零,sb 的所有引用永遠(yuǎn)不會(huì) “逃逸” 到該方法之外瘪松,其他線程無法訪問到它。因此锨阿,雖然這里有鎖宵睦,但是可以被安全地消除掉,在即時(shí)編譯之后墅诡,這段代碼就會(huì)忽略掉所有的同步而直接執(zhí)行了壳嚎。
13.3.3 鎖粗化
- 原則上,我們?cè)诰帉懘a的時(shí)候末早,總是推薦將同步塊的作用范圍限制得盡量小——只在共享數(shù)據(jù)的實(shí)際作用域中才進(jìn)行同步烟馅,這樣是為了使得需要同步的操作數(shù)量盡可能變小,如果存在鎖競(jìng)爭(zhēng)然磷,那等待鎖的線程也能盡快拿到鎖郑趁。
- 大部分情況下,上面的原則都是正確的姿搜,但是如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖寡润,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,那即使沒有線程競(jìng)爭(zhēng)舅柜,頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗梭纹。
- 下列代碼中連續(xù)的
append()
方法就屬于這類情況。如果虛擬機(jī)探測(cè)到有這樣一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖致份,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部变抽。以下列代碼為例,就是擴(kuò)展到第一個(gè)append()
操作之前直至最后一個(gè)append()
操作之后氮块,這樣只需要加鎖一次就可以了绍载。
public String concatString (String s1, String s2, string s3)
{
StringBuffer sb = new StringBuffer();
// lock
sb.append(s1);
sb.append(s2);
sb.append(s3);
// unlock
return sb.toString();
}
13.3.4 輕量級(jí)鎖
- 輕量級(jí)鎖是 JDK1.6 之中加入的新型鎖機(jī)制,它名字中的 “輕量級(jí)” 是相對(duì)于使用操作系統(tǒng)互斥量來實(shí)現(xiàn)的傳統(tǒng)鎖而言的滔蝉,因此傳統(tǒng)的鎖機(jī)制就稱為 “重量級(jí)” 鎖逛钻。
- 首先需要強(qiáng)調(diào)一點(diǎn)的是,輕量級(jí)鎖并不是用來代替重量級(jí)鎖的锰提,它的本意是在沒有多線程競(jìng)爭(zhēng)的前提下,減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗芳悲。
- HotSpot 虛擬機(jī)的對(duì)象頭(Object Header)分為兩部分信息:
- 第一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)立肘,如哈希碼、GC分代年齡等名扛,官方稱它為 “Mark Word”谅年,它是實(shí)現(xiàn)輕量級(jí)鎖和偏向鎖的關(guān)鍵。
- 另外一部分用于存儲(chǔ)指向方法區(qū)對(duì)象類型數(shù)據(jù)的指針肮韧,如果是數(shù)組對(duì)象的話融蹂,還會(huì)有一個(gè)額外的部分用于存儲(chǔ)數(shù)組長(zhǎng)度旺订。
- 在代碼進(jìn)入同步塊的時(shí)候,如果此同步對(duì)象沒有被鎖定(鎖標(biāo)志位為 “01” 狀態(tài))超燃,虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間区拳,用于存儲(chǔ)鎖對(duì)象目前的 Mark Word的拷貝(官方把這份拷貝加了一個(gè)Displaced前綴,即Displaced Mark Word)意乓,這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如下:
- 然后樱调,虛擬機(jī)將使用 CAS 操作嘗試將對(duì)象的 Mark Word 更新為指向 Lock Record 的指針:
- 如果這個(gè)更新動(dòng)作成功了,那么這個(gè)線程就擁有了該對(duì)象的鎖届良,并且對(duì)象Mark Word的鎖標(biāo)志位(Mark Word的最后2bit)將轉(zhuǎn)變?yōu)?“00”笆凌,即表示此對(duì)象處于輕量級(jí)鎖定狀態(tài)。
- 如果這個(gè)更新操作失敗了士葫,虛擬機(jī)首先會(huì)檢查對(duì)象的 Mark Word 是否指向當(dāng)前線程的棧幀 :
- 如果是說明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖乞而,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行;
- 否則說明這個(gè)鎖對(duì)象已經(jīng)被其他線程搶占了慢显。如果有兩條以上的線程爭(zhēng)用同一個(gè)鎖爪模,那輕量級(jí)鎖就不再有效,要膨脹為重量級(jí)鎖鳍怨,鎖標(biāo)志的狀態(tài)值變?yōu)?“10”
13.3.5 偏向鎖
- 偏向鎖也是 JDK1.6 中引入的一項(xiàng)鎖優(yōu)化呻右,它的目的是消除數(shù)據(jù)在無競(jìng)爭(zhēng)情況下的同步原語,進(jìn)一步提高程序的運(yùn)行性能鞋喇。如果說輕量級(jí)鎖是在無競(jìng)爭(zhēng)的情況下使用 CAS 操作去消除同步使用的互斥量声滥,那偏向鎖就是在無競(jìng)爭(zhēng)的情況下把整個(gè)同步都消除掉,連 CAS 操作都不做了侦香。
- 偏向鎖的意思是這個(gè)鎖會(huì)偏向于第一個(gè)獲得它的線程落塑,如果在接下來的執(zhí)行過程中,該鎖沒有被其他的線程獲取罐韩,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步憾赁。
- 當(dāng)鎖對(duì)象第一次被線程獲取的時(shí)候:
- 虛擬機(jī)將會(huì)把對(duì)象頭中的標(biāo)志位設(shè)為 “01”,即偏向模式散吵;
- 同時(shí)使用 CAS 操作把獲取到這個(gè)鎖的線程的 ID 記錄在對(duì)象的Mark Word之中:
- 如果CAS操作成功龙考,持有偏向鎖的線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊時(shí),虛擬機(jī)都可以不再進(jìn)行任何同步操作
- 當(dāng)有另外一個(gè)線程去嘗試獲取這個(gè)鎖時(shí)矾睦,偏向模式就宣告結(jié)束晦款。根據(jù)鎖對(duì)象目前是否處于被鎖定的狀態(tài),撤銷偏向(Revoke Bias)后恢復(fù)到未鎖定(標(biāo)志位為 “01”)或輕量級(jí)鎖定(標(biāo)志位為 “00”)的狀態(tài)枚冗,后續(xù)的同步操作就如上面介紹的輕量級(jí)鎖那樣執(zhí)行缓溅。