0. 文章中的名詞解析
由于多線程中最復(fù)雜的就是很多看不懂的名詞,這里我將這些名詞匯總一下并總結(jié)在下文中
1. 偏向鎖
當(dāng)只有一個線程處于臨界區(qū)的時候,此時持有是鎖將呈現(xiàn)偏向鎖結(jié)構(gòu)
上面這篇文章使用了一個例子告訴我們什么是偏向鎖: 總結(jié)下來就是某個鎖偏向于某個線程,當(dāng)線程多次持有一個鎖的時候,這個時候為了不浪費時間做上線文切換,轉(zhuǎn)而用了這種偏向鎖結(jié)構(gòu),既只需要對比一下這個鎖之前持有者與現(xiàn)在要持有該鎖的線程是否是同一個即可。如果是一樣的,那就繼續(xù)將鎖給到它,如果不是一樣的,那么這個時候鎖就要進行第一次膨脹:膨脹為輕量級鎖
偏向鎖的目的在于優(yōu)化單線程執(zhí)行臨界區(qū)時,切換上下文與用戶和內(nèi)核態(tài)的情況。既這段代碼就一個線程A在執(zhí)行,然而卻不斷執(zhí)行那些切換工作讓本來執(zhí)行效率極高的代碼變得非常緩慢
1.1 偏向鎖內(nèi)部原理
1.2 優(yōu)點
優(yōu)點就是優(yōu)化了一個線程持有鎖的性能
1.3 缺點
如果明顯存在其他線程申請鎖,那么偏向鎖將很快膨脹為輕量級鎖。
2. 輕量級鎖
多個線程交替進入臨界區(qū)的時候,此時的鎖結(jié)構(gòu)為輕量級鎖
輕量級鎖是由偏向鎖升級而來,是當(dāng)有多個線程開始輪流持有鎖的時候,就會開啟輕量級鎖。
輕量級鎖的目標是,減少無實際競爭情況下,使用重量級鎖產(chǎn)生的性能消耗。雖然多個線程都在臨界區(qū),但是,他們并不干擾,A線程執(zhí)行完,B再接著執(zhí)行,看似是有競爭,但是它們一個上日班一個上夜班,互不干擾,這個時候他們持有的鎖就是輕量級鎖。那么如果A與B開始并不這樣交替持有了,改為競爭,那么這個時候鎖將膨脹成重量級鎖——既通過競爭的方式競爭鎖。
2.1 缺點
如果鎖競爭激烈,那么輕量級將很快膨脹為重量級鎖,那么維持輕量級鎖的過程就成了浪費。
3. 自旋鎖
鎖膨脹后,為了避免線程真實地在操作系統(tǒng)層面掛起,虛擬機還會做最后的努力—自旋鎖。當(dāng)前線程暫時無法獲得鎖,而且什么時候可以獲得鎖是一個未知數(shù),也許在幾個CPU時鐘周期后就可以得到鎖。如果這樣,簡單粗暴地掛起線程可能是一種得不償失的操作。系統(tǒng)會假設(shè)在不久的將來,線程可以得到這把鎖。因此,虛擬機會讓當(dāng)前線程做幾個空循環(huán)(這也是自旋的含義),在經(jīng)過若干次循環(huán)后,如果可以得到鎖,那么就順利進入臨界區(qū)。如果還不能獲得鎖,才會真的將線程在操作系統(tǒng)層面掛起。
3.1 優(yōu)點
- 自旋鎖不會使線程狀態(tài)發(fā)生切換,一直處于用戶態(tài),即線程一直都是active的;不會使線程進入阻塞狀態(tài),減少了不必要的上下文切換,執(zhí)行速度快
- 非自旋鎖在獲取不到鎖的時候會進入阻塞狀態(tài),從而進入內(nèi)核態(tài),當(dāng)獲取到鎖的時候需要從內(nèi)核態(tài)恢復(fù),需要線程上下文切換。 (線程被阻塞后便進入內(nèi)核(Linux)調(diào)度狀態(tài),這個會導(dǎo)致系統(tǒng)在用戶態(tài)與內(nèi)核態(tài)之間來回切換,嚴重影響鎖的性能)
3.2 缺點
- 如果某個線程持有鎖的時間過長,就會導(dǎo)致其它等待獲取鎖的線程進入循環(huán)等待,消耗CPU。使用不當(dāng)會造成CPU使用率極高。
- Java實現(xiàn)的自旋鎖不是公平的,即無法滿足等待時間最長的線程優(yōu)先獲取鎖。不公平的鎖就會存在“線程饑餓”問題。
4. 鎖消除
這個是通過JIT編譯器通過逃逸分析,分析鎖是否能夠合并成一把鎖的優(yōu)化方式。
public void method(){
Vector<String> v = new Vector<>();
for(int i=0; i < 100; i++){
v.add((Integer)i.toString());
}
}
如果不優(yōu)化,那么vector是一個持有鎖的結(jié)構(gòu),如果重復(fù)去操作添加元素,那么就會頻繁的切換上下文,然而,這個結(jié)構(gòu)僅僅只是作用在方法中,也就是其他線程根本不可能侵入,這個Vector并不會存在多線程的情況,那么通過JIT的逃逸分析,發(fā)現(xiàn)vector是該方法創(chuàng)建的,不存在多線程進入,那么就不需要鎖,于是就可以將鎖操作消除。