一、多線程中的鎖
在多線程中岛蚤,鎖就是要實現(xiàn)線程對資源的訪問控制邑狸。從程序的角度來看,鎖就是一個對象涤妒,這個對象需要完成以下幾個事情
- 記錄該對象是否被某個線程占用单雾;
- 如果被某個線程占用,還需記錄這個線程的 Thread ID;
- 記錄其他阻塞硅堆、等待拿到這個鎖的線程的集合屿储,在當(dāng)前線程釋放鎖后,從該集合中取出一個線程喚醒渐逃;
1.1 鎖的分類
樂觀鎖 和 悲觀鎖
樂觀鎖顧名思義就是一種樂觀的思想,即每次拿取數(shù)據(jù)時認(rèn)為其他線程不會修改疯潭,所以不會上鎖面殖,但是在更新時會判斷在此期間有沒有其他線程更新該數(shù)據(jù)畜普,采取在寫入時先讀取當(dāng)前版本號進(jìn)行比較,如果版本號相同钝荡,則更新舶衬,如果不同,則重復(fù)讀取-比較-寫的操作端辱。
樂觀鎖適用于多讀的應(yīng)用類型舞蔽,這樣可以提高吞吐率码撰。Java 中的樂觀鎖基本上都是通過 CAS(Compare And Swap 比較并交換)實現(xiàn)。
悲觀鎖顧名思義就是一種悲觀的思想朵栖,即每次拿取數(shù)據(jù)時都會認(rèn)其他線程會修改陨溅,所以在每次讀取數(shù)據(jù)時都會上鎖绍在,這樣其他線程想要讀取該數(shù)據(jù)時就會 block雹有,直到拿到鎖件舵。
悲觀鎖適用于多寫的應(yīng)用類型铅祸,Java 中 synchronized 關(guān)鍵字的實現(xiàn)就是悲觀鎖合武,AQS 包下的鎖則是先嘗試樂觀鎖去獲取鎖,獲取不到盟庞,才會轉(zhuǎn)換為悲觀鎖什猖,如 RetreenLock红淡。
共享鎖 和 排它鎖
排它鎖又稱為獨占鎖、獨享鎖,Java 中大部分的鎖都是排它鎖桶蝎。
共享鎖又被稱為讀鎖登渣,獲得共享鎖后,可以查看但是不能修改和刪除數(shù)據(jù)胜茧,其他線程此時也可以獲取到共享鎖竹揍,也可以查看但是不能修改和刪除數(shù)據(jù)芬位。
Java 中共享鎖和排它鎖的典型是讀寫鎖 ReentrantReadWriteLock
带到,其中讀鎖是共享鎖,寫鎖是獨享鎖被饿,兩個鎖不會同時出現(xiàn),即要么有一個或多個線程同時持有讀鎖闪金,要么只有一個線程持有寫鎖论颅。
公平鎖 和 非公平鎖
公平鎖是指按照線程請求順序來分配鎖。
非公平是指多個線程獲取鎖的順序并不是按照申請鎖的順序漏设,有可能后申請的線程優(yōu)先于先申請的線程獲得鎖郑口,也有可能造成優(yōu)先級反轉(zhuǎn)或線程饑餓(即每次釋放就會被插隊搶鎖盾鳞,從而導(dǎo)致某個線程在很長一段時間搶不到鎖,始終無法執(zhí)行)雁仲。
Java 中默認(rèn)的都是非公平鎖,在線程啟動后會嘗試插隊去搶鎖攒砖,如果沒有搶到吹艇,則再排隊。這樣的設(shè)計主要是為了 提高效率受神,避免線程喚醒時帶來的空檔期鼻听。
自旋鎖
自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖撑教,這樣能有效的減少線程上下文切換的消耗醉拓,但是會消耗 CPU收苏。
如果該線程一直獲取不到鎖鹿霸,將導(dǎo)致一直占用 CPU 自旋懦鼠,因此需要設(shè)定自旋等待的最大時間。在 JDK 1.6 引入了適應(yīng)性自旋鎖葛闷,即自旋等待的最大時間由前一次在同一個鎖上的自旋時間以及鎖持有者的狀態(tài)來決定淑趾,通過 -XX:+UseSpinning 開啟扣泊,-XX:PreBlockSpin=n 設(shè)置自旋的次數(shù)嘶摊;在 JDK 1.7 后,去掉了該參數(shù)阱飘,由 JVM 控制虱颗。
可重入鎖
可重入鎖又叫遞歸鎖忘渔,是指同一個線程在外層方法獲得鎖的時候,在進(jìn)入其內(nèi)部方法會自動獲取該鎖畦粮。
Java 中 Synchronized 和 ReetrantLock 都是可重入鎖宣赔。可重入鎖可以有效避免死鎖問題吏祸。
死鎖
當(dāng)兩個(或更多)線程(或進(jìn)程)互相持有對方所需的資源,又不主動釋放犁罩,導(dǎo)致所有線程(或進(jìn)程)無法繼續(xù)執(zhí)行床估,導(dǎo)致程序陷入無盡的阻塞诱渤,這就是死鎖,如下圖所示:
產(chǎn)生死鎖的條件:
- 互斥條件:每個資源只能被一個線程(或進(jìn)程)使用,其他線程(或進(jìn)程)要使用該資源赡茸,需要等待該資源被釋放;
- 請求與保持條件:一個線程因請求資源而阻塞時遗菠,則需要對已獲得的資源保持不放辙纬;
- 不剝奪條件:線程已獲得的資源叭喜,在未使用完之前贺拣,不會被強(qiáng)行剝奪譬涡;
- 循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系昂儒;
必然死鎖的代碼:
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
new Thread(() -> {
synchronized(A) {
try {
TimeUnit.SECONDS.sleep(1);
synchronized (B) {
System.out.println(Thread.currentThread().getName() + "獲取到了兩把鎖");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
synchronized(B) {
try {
TimeUnit.SECONDS.sleep(1);
synchronized (A) {
System.out.println(Thread.currentThread().getName() + "獲取到了兩把鎖");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}