程序產(chǎn)生異常杭措,鎖就會被釋放拳话。
原子性:某個操作是不可分割的珍剑。在一個線程進行對代碼塊原子操作的時候钓辆,其他的線程必須等待該線程完成才能進行操作缕溉。
可見性:當一個線程對某個值進行修改的時候考传,其他線程得到的也是剛剛被修改過的最新的值。
volatile保證了變量的可見性证鸥,但是并不保證其原子性僚楞。
當線程修改了變量的時候勤晚,這個變量會被立刻寫回內(nèi)存,
而其他變量在使用這個變量的時候泉褐,直接從內(nèi)存里面讀取赐写,從而保證其他線程在使用這個變量的時候拿到的是最新的數(shù)據(jù)。
synchronized膜赃,保證了原子性以及可見性挺邀。除此之外,synchronized還是可重入鎖跳座。
可重入鎖的基本理解:
可重入就是說某個線程已經(jīng)獲得某個鎖端铛,可以再次獲取鎖而不會出現(xiàn)死鎖。加鎖方法之間相互調(diào)用或者存在繼承關(guān)系的調(diào)用時會用到這個概念疲眷。
public class demo3 {
public synchronized void m1()
{
this.m2();//此時該對象已經(jīng)獲取到了鎖禾蚕,但是調(diào)用m2時需要再次獲得鎖,如果不支持可重入的話就會發(fā)生死鎖現(xiàn)象
System.out.println("m1");
}
public synchronized void m2()
{
System.out.println("m2");
}
public static void main(String[] args) {
demo3 d=new demo3();
new Thread(()->d.m1()).start();
}
}
鎖升級機制:
synchronized在jdk1.6之前是重量級鎖狂丝,依靠于操作系統(tǒng)實現(xiàn)换淆,效率低。
阻塞或喚醒一個Java線程需要操作系統(tǒng)切換CPU狀態(tài)來完成几颜,這種狀態(tài)轉(zhuǎn)換需要耗費處理器時間产舞。如果同步代碼塊中的內(nèi)容過于簡單,狀態(tài)轉(zhuǎn)換消耗的時間有可能比用戶代碼執(zhí)行的時間還要長菠剩。
java通過鎖升級機制解決這個問題易猫。
偏向鎖---》自旋鎖--》重量級鎖
- 偏向鎖:一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖具壮,降低獲取鎖的代價准颓。在大多數(shù)情況下,鎖總是由同一線程多次獲得棺妓,不存在多線程競爭攘已,所以出現(xiàn)了偏向鎖。其目標就是在只有一個線程執(zhí)行同步代碼塊時能夠提高性能怜跑,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖样勃。
- 自旋鎖:當鎖處于偏向鎖的狀態(tài),出現(xiàn)了其他線程想要獲取這個鎖性芬,此時鎖的狀態(tài)會變?yōu)樽孕i峡眶,第二個線程會處于自旋狀態(tài)等待正在占有鎖的進程,不需要再被掛起處于阻塞狀態(tài)(不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換)植锉。
- 重量級鎖:若當前只有一個等待線程辫樱,則該線程通過自旋進行等待。但是當自旋超過一定的次數(shù)俊庇,或者一個線程在持有鎖狮暑,又有大量線程想持有這個鎖鸡挠,此時CPU的資源會被過多浪費,自旋鎖會轉(zhuǎn)變?yōu)橹亓考夋i搬男,將所有沒有申請到該鎖的線程阻塞掛起拣展。
JMM-java內(nèi)存模型
示意圖
一個線程如果要讀取主內(nèi)存中的變量,首先要從主內(nèi)存中復制一個副本到線程專屬的工作內(nèi)存中缔逛,修改過后將工作內(nèi)存中的變量覆蓋到主內(nèi)存中瞎惫,就完成了對主內(nèi)存中數(shù)據(jù)的修改。當多個線程訪問同一個變量的時候译株,某一個線程修改了之后瓜喇,還沒有進行覆蓋,另一個線程就又訪問了這個變量歉糜,此時數(shù)據(jù)就沒有同步乘寒,而volatile保證了這個變量一旦被修改,就立刻寫回主存匪补,此時其他線程再訪問時伞辛,保證了數(shù)據(jù)的同步。