Java中鎖的概念
- 自旋鎖:為了不放棄CPU執(zhí)行事件,循環(huán)的使用CAS技術(shù)對(duì)數(shù)據(jù)嘗試進(jìn)行更新,直至成功。
- 悲觀鎖:假定會(huì)發(fā)生并發(fā)沖突,同步所有對(duì)數(shù)據(jù)的相關(guān)操作,從讀數(shù)據(jù)就開始上鎖。
- 樂觀鎖:假定沒有沖突,在修改數(shù)據(jù)時(shí)如果發(fā)現(xiàn)數(shù)據(jù)和之前獲取的不一致,則讀最新數(shù)據(jù),修改后重試修改。
- 獨(dú)享鎖(寫):給資源加上寫鎖,線程可以修改資源,其他線程不能再加鎖。(單寫)
- 共享鎖(讀):給資源加上讀鎖后只能讀不能改,其他線程也只能加讀鎖,不能加寫鎖。(多讀)
- 可重入鎖、不可重入鎖:線程拿到一把鎖之后,可以自由進(jìn)入同一把鎖所同步的其他代碼。
- 公平鎖、非公平鎖:爭(zhēng)搶鎖的順序,如果是按先來后到,則為公平。
幾種重要的鎖實(shí)現(xiàn)方式:Synchronized、ReentrantLock、ReentrantReadWriteLock。
同步關(guān)鍵字synchronized
屬于最基本的線程通信機(jī)制,基于對(duì)象監(jiān)視器實(shí)現(xiàn)的。
Java中的每個(gè)對(duì)象都與一個(gè)監(jiān)視器相關(guān)聯(lián),一個(gè)線程可以鎖定或解鎖。
一次只有一個(gè)線程可以鎖定監(jiān)視器。
試圖鎖定該監(jiān)視器的任何其他線程都會(huì)被阻塞,直到它們可以獲得該監(jiān)視器上的鎖定為止。
特性: 可重入、獨(dú)享、悲觀鎖
鎖的范圍: 類鎖、對(duì)象鎖、鎖消除、鎖粗化。
提示:同步關(guān)鍵字,不僅是實(shí)現(xiàn)同步,根據(jù)JMM規(guī)定還能保證可見性(讀取最新主內(nèi)存數(shù)據(jù),結(jié)束后寫入主內(nèi)存)。
同步關(guān)鍵字加鎖原理
同步關(guān)鍵字加鎖原理 - 輕量級(jí)鎖
在這里插入圖片描述
偏向鎖到輕量級(jí)鎖
重量級(jí)鎖 - 監(jiān)視器(monitor)
過程解析
第一次取得鎖時(shí)會(huì)在對(duì)象的Markword標(biāo)志為偏向鎖,此時(shí)同一個(gè)用戶來使用,不用進(jìn)行取鎖操作,直到有其他的用戶也想取這個(gè)鎖,對(duì)象的Markword標(biāo)志就會(huì)升級(jí)為輕量級(jí)鎖,若上一個(gè)用戶正在使用,這個(gè)用戶自旋重鎖,自旋CAS,若沒有在使用,則直接開始競(jìng)爭(zhēng),若在并發(fā)高時(shí),上個(gè)用戶使用時(shí)間過長(zhǎng),這個(gè)用戶不斷的自旋重鎖達(dá)到一定的次數(shù)(可以自己設(shè)置),則會(huì)升級(jí)為重量級(jí)鎖,在每次自旋的時(shí)候會(huì)等待一段時(shí)間,以節(jié)約CPU資源。
鎖消除、鎖粗化
- 鎖消除:在熱點(diǎn)代碼,運(yùn)行時(shí)Jit編譯優(yōu)化,將兩個(gè)鎖變成一個(gè)更大的鎖包含兩個(gè)內(nèi)容。
- 鎖粗化:在熱點(diǎn)代碼,運(yùn)行時(shí)Jit編譯優(yōu)化,把鎖消除,進(jìn)入時(shí)不需要監(jiān)視器。
類鎖和對(duì)象鎖
我們都知道類的對(duì)象實(shí)例可以有很多個(gè),但是每個(gè)類只有一個(gè)class對(duì)象,所以不同對(duì)象實(shí)例的對(duì)象鎖是互不干擾的,但是每個(gè)類只有一個(gè)類鎖。
靜態(tài)方法鎖和方法鎖的區(qū)別就在于,類的每個(gè)對(duì)象靜態(tài)方法是一樣的,所以靜態(tài)方法鎖相當(dāng)于類鎖,而類的每個(gè)對(duì)象方法是不一樣的,所以方法鎖就相當(dāng)于對(duì)象鎖。
//對(duì)象鎖
public class ObjectSyncDemo1 {
public void test1() {
synchronized (this) {
try {
System.out.println(Thread.currentThread() + " 我開始執(zhí)行");
Thread.sleep(3000L);
System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束");
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
new ObjectSyncDemo1().test1();
}).start();
Thread.sleep(1000L); // 等1秒鐘,讓前一個(gè)線程啟動(dòng)起來
new Thread(() -> {
new ObjectSyncDemo1().test1();
}).start();
}
}
輸出結(jié)果:
Thread[Thread-0,5,main] 我開始執(zhí)行
Thread[Thread-1,5,main] 我開始執(zhí)行
Thread[Thread-0,5,main] 我執(zhí)行結(jié)束
Thread[Thread-1,5,main] 我執(zhí)行結(jié)束
//類鎖
public class ObjectSyncDemo1 {
public void test1() {
synchronized (ObjectSyncDemo1.class) {
try {
System.out.println(Thread.currentThread() + " 我開始執(zhí)行");
Thread.sleep(3000L);
System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束");
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
new ObjectSyncDemo1().test1();
}).start();
Thread.sleep(1000L); // 等1秒鐘,讓前一個(gè)線程啟動(dòng)起來
new Thread(() -> {
new ObjectSyncDemo1().test1();
}).start();
}
}
輸出結(jié)果:
Thread[Thread-0,5,main] 我開始執(zhí)行
Thread[Thread-0,5,main] 我執(zhí)行結(jié)束
Thread[Thread-1,5,main] 我開始執(zhí)行
Thread[Thread-1,5,main] 我執(zhí)行結(jié)束
//方法鎖
public class ObjectSyncDemo1 {
public synchronized void test1() {
try {
System.out.println(Thread.currentThread() + " 我開始執(zhí)行");
Thread.sleep(3000L);
System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束");
} catch (InterruptedException e) {
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
new ObjectSyncDemo1().test1();
}).start();
Thread.sleep(1000L); // 等1秒鐘,讓前一個(gè)線程啟動(dòng)起來
new Thread(() -> {
new ObjectSyncDemo1().test1();
}).start();
}
}
輸出結(jié)果:
Thread[Thread-0,5,main] 我開始執(zhí)行
Thread[Thread-1,5,main] 我開始執(zhí)行
Thread[Thread-0,5,main] 我執(zhí)行結(jié)束
Thread[Thread-1,5,main] 我執(zhí)行結(jié)束
//靜態(tài)方法鎖
public class ObjectSyncDemo1 {
public static synchronized void test1() {
try {
System.out.println(Thread.currentThread() + " 我開始執(zhí)行");
Thread.sleep(3000L);
System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束");
} catch (InterruptedException e) {
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
new ObjectSyncDemo1().test1();
}).start();
Thread.sleep(1000L); // 等1秒鐘,讓前一個(gè)線程啟動(dòng)起來
new Thread(() -> {
new ObjectSyncDemo1().test1();
}).start();
}
}
輸出結(jié)果:
Thread[Thread-0,5,main] 我開始執(zhí)行
Thread[Thread-0,5,main] 我執(zhí)行結(jié)束
Thread[Thread-1,5,main] 我開始執(zhí)行
Thread[Thread-1,5,main] 我執(zhí)行結(jié)束
synchronized的缺陷:當(dāng)某個(gè)線程進(jìn)入同步方法獲得對(duì)象鎖,那么其他線程訪問這里對(duì)象的同步方法時(shí),必須等待或者阻塞,這對(duì)高并發(fā)的系統(tǒng)是致命的,這很容易導(dǎo)致系統(tǒng)的崩潰。如果某個(gè)線程在同步方法里面發(fā)生了死循環(huán),那么它就永遠(yuǎn)不會(huì)釋放這個(gè)對(duì)象鎖,那么其他線程就要永遠(yuǎn)的等待。這是一個(gè)致命的問題。
當(dāng)然同步方法和同步代碼塊都會(huì)有這樣的缺陷,只要用了synchronized關(guān)鍵字就會(huì)有這樣的風(fēng)險(xiǎn)和缺陷。既然避免不了這種缺陷,那么就應(yīng)該將風(fēng)險(xiǎn)降到最低。這也是同步代碼塊在某種情況下要優(yōu)于同步方法的方面。例如在某個(gè)類的方法里面:這個(gè)類里面聲明了一個(gè)對(duì)象實(shí)例,SynObject so=new SynObject();在某個(gè)方法里面調(diào)用了這個(gè)實(shí)例的方法so.testsy();但是調(diào)用這個(gè)方法需要進(jìn)行同步,不能同時(shí)有多個(gè)線程同時(shí)執(zhí)行調(diào)用這個(gè)方法。
這時(shí)如果直接用synchronized修飾調(diào)用了so.testsy();代碼的方法,那么當(dāng)某個(gè)線程進(jìn)入了這個(gè)方法之后,這個(gè)對(duì)象其他同步方法都不能給其他線程訪問了。假如這個(gè)方法需要執(zhí)行的時(shí)間很長(zhǎng),那么其他線程會(huì)一直阻塞,影響到系統(tǒng)的性能。
如果這時(shí)用synchronized來修飾代碼塊:synchronized(so){so.testsy();},那么這個(gè)方法加鎖的對(duì)象是so這個(gè)對(duì)象,跟執(zhí)行這行代碼的對(duì)象沒有關(guān)系,當(dāng)一個(gè)線程執(zhí)行這個(gè)方法時(shí),這對(duì)其他同步方法時(shí)沒有影響的,因?yàn)樗麄兂钟械逆i都完全不一樣。
可重入鎖
public class ObjectSyncDemo2 {
public synchronized void test1(Object arg) {
System.out.println(Thread.currentThread() + " 我開始執(zhí)行 " + arg);
if (arg == null) {
test1(new Object());
}
System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束" + arg);
}
public static void main(String[] args) throws InterruptedException {
new ObjectSyncDemo2().test1(null);
}
}
輸出結(jié)果:
Thread[main,5,main] 我開始執(zhí)行 null
Thread[main,5,main] 我開始執(zhí)行 java.lang.Object@31b7dea0
Thread[main,5,main] 我執(zhí)行結(jié)束java.lang.Object@31b7dea0
Thread[main,5,main] 我執(zhí)行結(jié)束null
我們可以看出synchronized是個(gè)可重入鎖,在某個(gè)線程已經(jīng)獲得某個(gè)鎖,可以再次獲取鎖而不會(huì)出現(xiàn)死鎖。