目錄
認識Java對象頭
sychronized鎖原理
基本概念
工作原理?
1.作用在方法上
2.作用在代碼塊上
工作機制
JVM優化鎖
Monitor鎖
wait/notify
park/unpark
線程狀態轉換案例?
死鎖
概念
死鎖發生的必要條件
哲學家問題
活鎖
饑餓?
概念
饑餓的原因
ReentrantLock?
基本鎖操作
查看鎖狀態的方法
條件變量相關的方法
可重入鎖
可打斷(lockInterruptibly)
不可打斷?
鎖超時(tryLock)
立刻返回
??編輯
超時釋放?
公平鎖/非公平鎖?
非公平鎖
公平鎖
??編輯
?await/Condition
等待條件(await方法系列):
通知條件(signal方法系列):
認識Java對象頭
32位虛擬機對象頭:
64位虛擬機對象頭:?
1.Mark Word(標記字):
- Mark Word是對象頭的一部分,用于存儲對象自身的哈希碼、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID(或偏向時間戳)、偏向模式以及鎖的狀態等信息。
- 標記字的大小和具體內容可能因JVM實現的不同而有所變化。例如,在64位JVM上,默認情況下Mark Word占用64位(8字節),而在32位JVM上則是32位(4字節)。
2.Class Pointer(類指針):
- 這是指向該對象對應類(Class)的指針,通過這個指針可以訪問到對象所屬類的元數據(如方法表、字段描述等)。類指針的大小依賴于JVM的具體實現及其是否開啟了壓縮指針(Compressed Oop)選項。
- 在某些情況下,比如當使用了-XX:+UseCompressedClassPointers選項時,類指針會被壓縮以節省內存。
3.Array Length(數組長度,僅適用于數組對象):
- 如果對象是一個數組,則對象頭還需要額外的空間來存儲數組的長度。這是因為非數組對象不需要知道其“長度”,但數組需要這個信息以便進行邊界檢查等操作。
sychronized鎖原理
基本概念
- 對象頭(Object Header):每個Java對象都有一個對象頭,其中包含了一些元數據信息。對于普通對象來說,這部分包括Mark Word和Class Pointer。Mark Word存儲了對象的哈希碼、GC分代年齡、鎖狀態標志等信息。
- Monitor(監視器/管程):這是JVM內部的一種同步機制。每個對象都關聯有一個監視器鎖。當一個線程獲取到這個鎖時,它可以進入臨界區;其他試圖進入同一臨界區的線程必須等待,直到第一個線程釋放鎖。
工作原理?
1.作用在方法上
- 使用synchronized修飾一個實例方法時,該方法被調用時會自動獲取當前實例(即this)的monitor鎖。
- 如果是靜態方法,則鎖定的是該類的Class對象。
2.作用在代碼塊上
- 使用synchronized代碼塊可以更靈活地指定要鎖定的對象,獲取的是括號中的對象。
工作機制
字節碼層面:使用synchronized關鍵字時,編譯器會將同步塊或方法轉換為特定的字節碼指令。對于同步方法,編譯器會在方法信息中設置一個標志位(ACC_SYNCHRONIZED)。對于同步代碼塊,則會生成monitorenter和monitorexit指令。
monitorenter 和 monitorexit 指令:
- monitorenter:每個對象都有一個與之關聯的monitor(監視器鎖)。當線程執行到monitorenter指令時,它嘗試獲取該對象的monitor。如果monitor未被其他線程持有,則當前線程成功獲取monitor并繼續執行;否則,線程將被阻塞直到獲得monitor。
- monitorexit:當線程執行完同步代碼塊后,會執行monitorexit指令以釋放monitor,允許其他等待獲取該monitor的線程繼續執行。
public class Test2 {static final Object lock = new Object();static int cnt = 0;public static void main(String[] args) {synchronized (lock) {cnt ++;}}
}
上段代碼main方法生成的字節碼指令如下:
public static void main(java.lang.String[]);Code:0: getstatic #2 // Field lock:Ljava/lang/Object;3: dup4: astore_15: monitorenter // 獲取鎖6: getstatic #3 // Field cnt:I9: iconst_110: iadd11: putstatic #3 // Field cnt:I14: aload_115: monitorexit // 釋放鎖16: goto 2419: astore_220: aload_121: monitorexit // 確保在異常情況下也釋放鎖22: aload_223: athrow24: returnException table:from to target type6 16 19 any19 22 19 any
JVM優化鎖
1.無鎖狀態:
- 這是對象的初始狀態,沒有任何線程持有該對象的Monitor鎖。
2.偏向鎖(Biased Locking):
- 偏向鎖是針對單線程訪問場景的一種優化。它假設如果一個線程獲得了某個對象的鎖,那么接下來很可能還是這個線程繼續訪問該對象。
- 當一個線程第一次進入同步塊時,會嘗試獲取偏向鎖,并將線程ID記錄在對象頭中。如果之后還是同一個線程進入同步塊,則不需要再次獲取鎖,減少了鎖獲取的開銷。
- 如果有其他線程試圖獲取偏向鎖,則需要撤銷偏向鎖并升級為輕量級鎖。
3.輕量級鎖(Lightweight Locking):
- 當存在少量鎖競爭時,使用輕量級鎖來代替重量級鎖,以提高性能。
- 輕量級鎖通過CAS(Compare And Swap)操作嘗試原子性地獲得鎖。如果成功,則線程可以進入臨界區;如果失敗,表示存在鎖競爭,鎖可能會升級為重量級鎖。
4.重量級鎖(Heavyweight Locking):
- 當鎖競爭加劇時,JVM會將鎖升級為重量級鎖。重量級鎖涉及到操作系統層面的線程掛起和恢復操作,因此開銷較大。
- 在這種狀態下,所有競爭鎖的線程都會被阻塞,直到當前持有鎖的線程釋放鎖為止。
5.自旋鎖(Spin Lock):
- 自旋鎖不是一種獨立的鎖類型,而是一種策略。當一個線程嘗試獲取鎖時,它可以選擇“自旋”一段時間,而不是立即進入阻塞狀態。這樣可以在等待鎖釋放的過程中減少上下文切換的開銷。
- Java中的自旋鎖通常與輕量級鎖結合使用,在某些情況下可以作為一種優化手段。
6.可重入鎖(Reentrant Lock):
- synchronized機制本身就是可重入的,這意味著如果一個線程已經持有了某個對象的鎖,它可以再次獲取相同的鎖而不會導致死鎖。
- 每次重新獲取鎖時,計數器會增加;每次釋放鎖時,計數器會減少,直到計數器歸零才真正釋放鎖。
在Java中默認第一次給對象加鎖就是偏向鎖(默認延遲加載),假如t1線程已經給obj加上鎖(偏向鎖)了,當有另外一個線程t2也試圖獲取同一把鎖時,偏向鎖升級為輕量級鎖。
輕量級鎖通過使用CAS操作嘗試將對象頭中的Mark Word更新為指向當前線程的Lock Record的指針。如果成功,則線程獲得了輕量級鎖,并繼續執行;如果失敗(意味著存在鎖競爭),則會嘗試進行鎖膨脹,輕量級鎖嘗試升級為重量級鎖。
重量級鎖的核心是Monitor(監視器鎖),如果Monitor已被其他線程持有,則試圖獲取鎖的線程將被放入Entry Set中等待。此時,線程會進入阻塞狀態,即線程會被掛起,不會占用CPU資源,直到Monitor鎖被釋放并且該線程有機會重新嘗試獲取鎖,這允許Entry Set中的一個或多個線程重新競爭Monitor鎖。如果有線程處于Wait Set中,那么根據調用的是notify()還是notifyAll()方法,相應的線程會被移動到Entry Set中,重新參與Monitor鎖的競爭。
Monitor鎖
- Owner(所有者):指向當前持有Monitor的線程。
- Entry Set(入口集):等待獲取Monitor鎖的線程隊列。如果一個線程嘗試進入一個已經被其他線程持有的同步塊或方法,則該線程會被放入這個隊列中,直到Monitor被釋放并且該線程有機會重新嘗試獲取鎖。
- Wait Set(等待集):調用了wait()方法并正在等待某個條件滿足的線程集合。這些線程暫時釋放了Monitor,并且不會參與鎖的競爭,直到另一個線程調用notify()或notifyAll()方法通知它們可以繼續執行。
wait/notify
- wait():使當前線程等待,直到另一個線程調用同一個對象上的notify()或notifyAll()方法。調用wait()時,當前線程會釋放它持有的對象鎖,并進入該對象的“等待集”。只有當其他線程調用了相應的通知方法并且當前線程被選中(對于notify())或所有等待的線程都被喚醒(對于notifyAll()),當前線程才能重新獲取鎖并繼續執行。
- notify():隨機喚醒一個正在等待該對象監視器的單個線程。如果多個線程都在等待,則選擇其中一個線程進行喚醒。被喚醒的線程并不能立即執行,而是要等到當前線程退出同步代碼塊并釋放鎖之后。
- notifyAll():喚醒所有正在等待該對象監視器的線程。盡管所有線程都會被喚醒,但只有一個線程能夠成功獲得鎖并繼續執行;其余線程將再次進入等待狀態或者競爭鎖。
final static Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{synchronized (lock) {log.debug("running {}", System.currentTimeMillis());try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug("end... {}", System.currentTimeMillis());}},"t1");Thread t2 = new Thread(()->{synchronized (lock) {log.debug("running {}", System.currentTimeMillis());try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}log.debug("end... {}", System.currentTimeMillis());}}, "t2");t1.start();t2.start();Thread.sleep(2000);synchronized (lock) {log.debug("喚醒線程");// lock.notify(); // 喚醒一個線程lock.notifyAll(); // 喚醒所有線程}}
?調用wait就是當前線程得到了對象鎖了,但是我不往下執行了,我把鎖釋放給其他線程先去執行,我自己到Wait Set(等待集)中去,有人(線程)叫我(notify)我再去排隊進入Entry Set(入口集),每有人叫我我就繼續等著。(感覺很像坐高鐵,我雖然進了高鐵站,但是車次沒有輪到我,那么我就繼續等著,如果叫到我了我就去排隊等待坐車)
park/unpark
它們是 LockSupport 類中的方法
// 暫停當前線程
LockSupport.park();
// 恢復某個線程的運行
LockSupport.unpark(暫停線程對象)
@Slf4j(topic = "c.thread")
public class Test4 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("t1 running {}", System.currentTimeMillis());LockSupport.park();log.debug("t1 end {}", System.currentTimeMillis());}, "t1");t1.start();sleep(1000);LockSupport.unpark(t1);}
}
?執行結果:
16:10:58 [t1] c.thread - t1 running 1742631058230
16:10:59 [t1] c.thread - t1 end 1742631059240
線程狀態轉換案例?
@Slf4j(topic = "c.thread")
public class Test5 {private static final Object obj = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {synchronized (obj) {log.debug("running t1");try {sleep(1000);obj.notify();} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2 = new Thread(() -> {synchronized (obj) {log.debug("running t2");try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}}, "t2");Thread t3 = new Thread(() -> {synchronized (obj) {log.debug("running t3");try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}}, "t2");t1.start();t2.start();t3.start();log.debug("t1 {} t2 {} t3 {}", t1.getState(), t2.getState(), t3.getState());sleep(100);log.debug("t1 {} t2 {} t3 {}", t1.getState(), t2.getState(), t3.getState());sleep(2000);log.debug("t1 {} t2 {} t3 {}", t1.getState(), t2.getState(), t3.getState());}
}
運行結果:
21:08:27 [main] c.thread - t1 RUNNABLE t2 RUNNABLE t3 RUNNABLE
21:08:27 [t2] c.thread - running t2
21:08:27 [t2] c.thread - running t3
21:08:27 [Thread-0] c.thread - running t1
21:08:27 [main] c.thread - t1 TIMED_WAITING t2 WAITING t3 WAITING
21:08:29 [main] c.thread - t1 TERMINATED t2 TERMINATED t3 WAITING?
第一次打印分析:三個線程都啟動了
第二次打印分析:此時t1在sleep,t2和t3都在wait
第三次打印分析:t1sleep完了,然后隨機喚醒一個waiting的線程,所以t1和t2都執行完了,t3沒有人喚醒它
死鎖
概念
死鎖(Deadlock)是指兩個或更多線程在執行過程中,由于爭奪資源而造成的一種互相等待的狀態。在這種狀態下,沒有任何線程能夠繼續前進,整個系統或部分系統因此陷入停滯。死鎖是并發編程中常見的問題之一,尤其是在多線程環境下對共享資源進行訪問時。
死鎖發生的必要條件
- 互斥條件:至少有一個資源必須處于非共享模式,即只能被一個線程占用。例如,文件寫入權限通常就是互斥的。
- 占有并等待條件:一個線程已經占有了至少一個資源,并且正在等待其他線程占有的資源。也就是說,該線程不會釋放自己已獲得的資源直到它獲得了所有需要的資源。
- 不可剝奪條件:資源不能被強制從某個線程中搶走,只能由擁有它的線程主動釋放。這意味著一旦一個線程獲得了某個資源,它可以在不自愿的情況下保持對該資源的控制權,直到它完成任務并釋放資源。
- 循環等待條件:存在一組等待資源的線程鏈,其中每個線程都在等待下一個線程當前占有的資源。例如,線程A等待線程B持有的資源,而線程B又在等待線程C持有的資源,以此類推,直到某一線程又在等待線程A持有的資源,形成一個閉環。?
一個線程同時獲取多把鎖就容易導致死鎖
@Slf4j(topic = "c.Test6")
public class Test6 {static Object lockA = new Object();static Object lockB = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {synchronized (lockA) {log.debug("lockA {}", Thread.currentThread().getName());synchronized (lockB) {log.debug("lockB {}", Thread.currentThread().getName());try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}, "t1");Thread t2 = new Thread(() -> {synchronized (lockB) {log.debug("lockB {}", Thread.currentThread().getName());synchronized (lockA) {log.debug("lockA {}", Thread.currentThread().getName());try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}, "t2");t1.start();t2.start();log.debug("t1 {} t2 {}", t1.getState(), t2.getState());sleep(100);log.debug("t1 {} t2 {}", t1.getState(), t2.getState());sleep(2000);log.debug("t1 {} t2 {}", t1.getState(), t2.getState());}}
分析:
t1獲得lockA的鎖,t2獲得lockB的鎖,t1、t2都上鎖成功,然后t1想要獲得lockB,但是lockB被t2占有,t2只有等到全部執行完才能釋放鎖,但是t2中又要獲得lockA的鎖,但是lockA的鎖被t1占有,又回到t1,t1也只能是執行完才能釋放鎖,然后t1和t2就互相等待對方放鎖,造成死鎖。?
?----> 定位死鎖的工具:jconsole
哲學家問題
- 有五位哲學家,圍坐在圓桌旁。
- 他們只做兩件事,思考和吃飯,思考一會吃口飯,吃完飯后接著思考。
- 吃飯時要用兩根筷子吃,桌上共有 5 根筷子,每位哲學家左右手邊各有一根筷子。
- 如果筷子被身邊的人拿著,自己就得等待
package cn.itcast.sychronized;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test7")
public class Test7 {public static void main(String[] args) {ChopSticks c1 = new ChopSticks("1");ChopSticks c2 = new ChopSticks("2");ChopSticks c3 = new ChopSticks("3");ChopSticks c4 = new ChopSticks("4");ChopSticks c5 = new ChopSticks("5");Philosopher p1 = new Philosopher("p1", c1, c2);Philosopher p2 = new Philosopher("p2", c2, c3);Philosopher p3 = new Philosopher("p3", c3, c4);Philosopher p4 = new Philosopher("p4", c4, c5);Philosopher p5 = new Philosopher("p5", c5, c1);p1.start();p2.start();p3.start();p4.start();p5.start();}
}
class ChopSticks {String name;public ChopSticks(String name) {this.name = name;}@Overridepublic String toString() {return "筷子 { " + name + "}";}
}
@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread{final ChopSticks left;final ChopSticks right;public Philosopher(String name, ChopSticks left, ChopSticks right) {super(name); // 線程名字this.left = left;this.right = right;}public void eat() {log.debug("{} eat", Thread.currentThread().getName());}@Overridepublic void run() {while (true) {synchronized (left) {synchronized (right) {eat();}}}}
}
活鎖
活鎖(Livelock)是并發編程中的一種問題,類似于死鎖,但它涉及的是線程雖然沒有被阻塞,但卻無法繼續向前推進的情況。在活鎖的情況下,線程不斷地嘗試執行某項操作但總是失敗,并且不斷地重復相同的操作,導致它們實際上沒有取得任何進展。這種現象通常發生在試圖避免死鎖的過程中,特別是當多個線程相互改變狀態以試圖解決另一個線程的問題時。
@Slf4j(topic = "c.Test8")
public class Test8 {static volatile int cnt = 0;public static void main(String[] args) {new Thread(() -> {while (cnt > 0) {cnt --;try {sleep(100);} catch (InterruptedException e) {e.printStackTrace();}log.debug("t1 : {}", cnt);}}, "t1").start();new Thread(() -> {while (cnt < 100) {cnt ++;try {sleep(100);} catch (InterruptedException e) {e.printStackTrace();}log.debug("t2 : {}", cnt);}}, "t2").start();}
}
饑餓?
概念
?“饑餓”(Starvation)是指一個線程由于無法獲得所需的資源而無法繼續執行的情況。盡管該線程沒有被阻塞,但由于其他線程頻繁地搶占資源,導致這個線程得不到足夠的CPU時間或其他必要的資源來推進其任務
饑餓的原因
- 資源競爭:當多個線程競爭有限的資源時,如果沒有公平的調度機制,某些線程可能會始終得不到資源,從而導致饑餓。例如,如果系統總是優先處理高優先級的任務,那么低優先級的任務可能永遠得不到執行的機會。
- 不恰當的鎖機制:在使用鎖進行同步控制時,如果某個線程長時間持有鎖而不釋放,其他等待該鎖的線程就會被阻塞,可能導致這些線程出現饑餓現象。
- 忙等待(Busy-waiting):如果一個線程通過忙等待的方式來等待某個條件成立,而這個條件很少或幾乎不會成立,那么這個線程就會一直消耗CPU資源,同時無法完成實際的工作。
- 不公平的調度器:操作系統或運行時環境中的線程調度器如果沒有實現公平調度算法,也可能導致某些線程長期得不到執行機會。?
ReentrantLock?
ReentrantLock reentrantLock = new ReentrantLock(true);
reentrantLock.lock();
try {
? ? // 臨界區代碼
} finally {
? ??reentrantLock.unlock()
}
ReentrantLock(false) : 非公平鎖
ReentrantLock(true) : 公平鎖
@Slf4j(topic = "c.Test")
public class Test {private static final ReentrantLock reentrantLock = new ReentrantLock(true);static int cnt = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {reentrantLock.lock();try {for (int i = 0; i < 10000; i++) {cnt++;}} finally {reentrantLock.unlock();}}, "t1");Thread t2 = new Thread(() -> {reentrantLock.lock();try {for (int i = 0; i < 10000; i++) {cnt--;}} finally {reentrantLock.unlock();}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("{}",cnt);}
}
基本鎖操作
lock():
- 獲取鎖。
- 如果鎖已經被其他線程持有,則當前線程將被阻塞,直到獲取到鎖為止。
lockInterruptibly() throws InterruptedException:
- 獲取鎖,但可以響應中斷。
- 如果當前線程在等待獲取鎖的過程中被中斷,則拋出 InterruptedException 異常并退出等待狀態。
tryLock():
- 嘗試獲取鎖,如果鎖未被其他線程持有,則立即返回 true;否則立即返回 false,不會阻塞當前線程。
tryLock(long timeout, TimeUnit unit) throws InterruptedException:
- 在給定的時間內嘗試獲取鎖。
- 如果在指定時間內成功獲取鎖,則返回 true;如果超時或者當前線程被中斷,則返回 false 或拋出 InterruptedException。
unlock():
- 釋放鎖。
- 每次調用 lock() 方法獲得一次鎖后,必須對應地調用一次 unlock() 來釋放鎖。通常在 finally 塊中調用以確保即使發生異常也能正確釋放鎖。
查看鎖狀態的方法
- isHeldByCurrentThread():判斷當前線程是否持有該鎖。
- getHoldCount():返回當前線程獲取這個鎖的次數(重入計數)。每次調用 lock() 方法都會增加計數,每次調用 unlock() 方法都會減少計數。
- isLocked():判斷鎖是否被任意一個線程持有。
- hasQueuedThreads():查詢是否有線程正在等待獲取此鎖。
條件變量相關的方法
newCondition():
- 創建一個新的 Condition 實例,與當前鎖關聯。
- 可以使用條件變量來實現類似于 Object.wait() 和 Object.notify() 的功能,但更加靈活和強大。
可重入鎖
這是指一個線程可以嘗試多次獲取同一個鎖,并且不會發生死鎖的情況。每次獲取鎖(調用lock()方法)都需要相應的釋放鎖(調用unlock()方法)。鎖獲取次數與鎖釋放次數需要匹配。
// ReentrantLock 可重入鎖
@Slf4j(topic = "c.Test2")
public class Test2 {final static ReentrantLock reentrantLock = new ReentrantLock();public static void method1() {reentrantLock.lock();try {log.debug("method1 running");method2();} finally {reentrantLock.unlock();}}public static void method2() {reentrantLock.lock();try {log.debug("method2 running");method3();} finally {reentrantLock.unlock();}}public static void method3() {reentrantLock.lock();try {log.debug("method3 running");} finally {reentrantLock.unlock();}}public static void main(String[] args) {method1();}
}
執行結果:
10:17:12 [main] c.Test2 - method1 running
10:17:12 [main] c.Test2 - method2 running
10:17:12 [main] c.Test2 - method3 running?
可打斷(lockInterruptibly)
這個方法允許你以一種可以響應中斷的方式獲取鎖。如果當前線程在調用lockInterruptibly()方法時沒有被打斷,并且能夠獲取到鎖,則它將繼續執行;但如果此時有另一個線程已經持有了這個鎖,那么當前線程將被阻塞,直到鎖可用或當前線程被中斷。
如果在等待獲取鎖的過程中收到了中斷請求(即另一個線程調用了當前線程的interrupt()方法),則會拋出InterruptedException異常,從而允許你捕獲這個異常并進行相應的處理。
// 可打斷
@Slf4j(topic = "c.Test3")
public class Test3 {static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("t1 running");try {lock.lockInterruptibly();} catch (InterruptedException e) {log.debug("t1:在等鎖過程中被打斷");e.printStackTrace();return;}try {log.debug("t1 獲取鎖 {}", System.currentTimeMillis());} finally {lock.unlock();}}, "t1");lock.lock();log.debug("主線程獲得鎖");t1.start();try {sleep(1000);t1.interrupt(); // 打斷t1log.debug("執行打斷");} finally {log.debug("主線程釋放鎖");lock.unlock();}}
}
不可打斷?
在ReentrantLock中,默認的鎖獲取方法lock()是不可中斷的。這意味著如果一個線程嘗試獲取一個已經被其他線程持有的鎖,它將會一直阻塞直到成功獲取到鎖,即使這個線程在這期間被中斷(即調用了該線程的interrupt()方法),也不會拋出InterruptedException,而是繼續等待直至獲得鎖。這種行為可以被視為“不可打斷”。
// 不可打斷
@Slf4j(topic = "c.Test4")
public class Test4 {static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("t1 啟動 {}", System.currentTimeMillis());lock.lock();try {log.debug("t1 獲得鎖 {}", System.currentTimeMillis());} finally {log.debug("t1 釋放鎖 {}", System.currentTimeMillis());lock.unlock();}}, "t1");lock.lock();log.debug("主線程獲得鎖 {}", System.currentTimeMillis());t1.start();try {log.debug("執行打斷 {}", System.currentTimeMillis());sleep(1000);t1.interrupt();} finally {log.debug("主線程釋放鎖 {}", System.currentTimeMillis());lock.unlock();}}
}
執行結果:
10:18:41 [main] c.Test4 - 主線程獲得鎖 1742696321911
10:18:41 [main] c.Test4 - 執行打斷 1742696321917
10:18:41 [t1] c.Test4 - t1 啟動 1742696321917
10:18:42 [main] c.Test4 - 主線程釋放鎖 1742696322931
10:18:42 [t1] c.Test4 - t1 獲得鎖 1742696322931
10:18:42 [t1] c.Test4 - t1 釋放鎖 1742696322931
鎖超時(tryLock)
- tryLock():嘗試立即獲取鎖。如果鎖當前沒有被其他線程持有,則該方法會成功獲取鎖并返回true;如果鎖已經被其他線程持有,則該方法不會阻塞,而是立刻返回false。
- tryLock(long timeout, TimeUnit unit):嘗試在指定的時間內獲取鎖。如果在這個時間段內成功獲取到了鎖,則返回true;如果在這個時間段內未能獲取到鎖(即鎖一直被其他線程持有),則返回false。此外,如果在等待期間當前線程被中斷,它將拋出InterruptedException。
立刻返回
// 鎖超時 --- 立刻返回
@Slf4j(topic = "c.Test5")
public class Test5 {static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("t1 running");if (!lock.tryLock()) {log.debug("t1 獲取鎖失敗, 立刻返回");return;}try {log.debug("t1 獲得鎖成功");} finally {lock.unlock();}}, "t1");lock.lock();t1.start();try {sleep(1000);} finally {lock.unlock();}}
}
?
超時釋放?
@Slf4j(topic = "c.Test5")
public class Test6 {static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {log.debug("t1 running");try {// 超時釋放if (!lock.tryLock(1, TimeUnit.SECONDS)) {log.debug("t1 1s嘗試獲得鎖失敗");return;}} catch (InterruptedException e) {e.printStackTrace();}try {log.debug("t1 獲得鎖成功");} finally {lock.unlock();}}, "t1");lock.lock();t1.start();try {sleep(1500);} finally {log.debug("主線程釋放鎖");lock.unlock();}}
}
?例子:哲學家問題
@Slf4j(topic = "c.Test7")
public class Test7 {public static void main(String[] args) {ChopSticks c1 = new ChopSticks("1");ChopSticks c2 = new ChopSticks("2");ChopSticks c3 = new ChopSticks("3");ChopSticks c4 = new ChopSticks("4");ChopSticks c5 = new ChopSticks("5");Philosopher p1 = new Philosopher("p1", c1, c2);Philosopher p2 = new Philosopher("p2", c2, c3);Philosopher p3 = new Philosopher("p3", c3, c4);Philosopher p4 = new Philosopher("p4", c4, c5);Philosopher p5 = new Philosopher("p5", c5, c1);p1.start();p2.start();p3.start();p4.start();p5.start();}
}class ChopSticks extends ReentrantLock {String name;public ChopSticks(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" + name + '}';}
}
@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {ChopSticks left;ChopSticks right;public Philosopher(String name, ChopSticks left, ChopSticks right) {super(name);this.left = left;this.right = right;}public void eat(){log.debug("{} eating", Thread.currentThread().getName());try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic void run() {while (true) {// 嘗試獲得左邊的筷子if (left.tryLock()) {try {// 嘗試獲得右邊的筷子if (right.tryLock()) {try {eat();} finally {right.unlock();}}} finally {left.unlock();}}}}
}
公平鎖/非公平鎖?
非公平鎖
- 定義:在非公平鎖機制下,鎖不會保證按照請求的先后順序來分配。當鎖可用時,任何一個嘗試獲取鎖的線程都有機會獲得鎖,包括那些剛剛釋放了這個鎖并且立刻再次嘗試獲取它的線程。
- 默認行為:如果不特別指定,ReentrantLock默認采用的是非公平鎖策略(即new ReentrantLock()等同于new ReentrantLock(false))。
- 性能:通常情況下,非公平鎖可以獲得更高的吞吐量,因為它減少了線程上下文切換的次數,并且允許線程在鎖剛被釋放時迅速重新獲取鎖,而不需要排隊等待。
// 非公平鎖
@Slf4j(topic = "c.Test8")
public class Test8 {static ReentrantLock lock = new ReentrantLock(false);public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 500; i ++) {new Thread(() -> {lock.lock();try {log.debug("{} 獲得鎖成功", Thread.currentThread().getName());} finally {lock.unlock();}}, "t" + i).start();}Thread.sleep(1);new Thread(()->{log.debug("start-----------------------");lock.lock();try {log.debug("{} 獲得鎖成功 running", Thread.currentThread().getName());} finally {lock.unlock();}}, "強行搶奪").start();}
}
?
公平鎖
- 定義:當使用公平鎖時,鎖會按照線程請求的順序來分配。也就是說,最早提出請求的線程將最先被授予鎖。這有助于避免“饑餓”現象,即某些線程永遠無法獲得鎖。
- 實現:要創建一個公平鎖的ReentrantLock實例,可以在構造函數中傳入參數true,例如new ReentrantLock(true)。
- 性能:由于需要維護線程請求鎖的順序,公平鎖可能會導致較低的吞吐量。因為每當有線程釋放鎖后,系統必須檢查等待隊列中的下一個線程是否可以獲取鎖,而不是簡單地允許當前正在嘗試獲取鎖的線程直接獲取它。
// 公平鎖
@Slf4j(topic = "c.Test8")
public class Test8 {static ReentrantLock lock = new ReentrantLock(true);public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 500; i ++) {new Thread(() -> {lock.lock();try {log.debug("{} 獲得鎖成功", Thread.currentThread().getName());} finally {lock.unlock();}}, "t" + i).start();}Thread.sleep(1);new Thread(()->{log.debug("start-----------------------");lock.lock();try {log.debug("{} 獲得鎖成功 running", Thread.currentThread().getName());} finally {lock.unlock();}}, "強行搶奪").start();}
}
?
?await/Condition
等待條件(await方法系列):
當一個線程需要等待某個特定條件發生時,可以調用Condition對象的await()方法進入等待狀態。這會導致當前線程釋放鎖并等待,直到另一個線程調用了同一個Condition對象的signal()或signalAll()方法。常見的await方法包括:
- await():使當前線程進入等待狀態,直到被通知或中斷。
- awaitUninterruptibly():類似于await(),但是不會響應中斷。
- awaitNanos(long nanosTimeout):嘗試等待指定的時間長度,如果超時則返回剩余時間。
- awaitUntil(Date deadline):等待直到指定的時間點,如果到達截止時間還未被通知,則繼續執行。
通知條件(signal方法系列):
當一個線程修改了共享資源并且認為可能滿足了一個或多個正在等待的線程的條件時,它可以調用Condition對象的signal()或signalAll()方法來喚醒等待的線程。
- signal():喚醒一個等待此Condition的線程。如果有多個線程在等待,則選擇其中的一個進行喚醒。
- signalAll():喚醒所有等待此Condition的線程。
可以把Condition實例看作對應的等待隊列,不同的線程可以調用不同的等待隊列的await方法,使得當前線程進入當前的等待隊列中?
?
// 等待隊列 Condition實例
// 喚醒線程:signal或signalAll
@Slf4j(topic = "c.Test9")
public class Test9 {static ReentrantLock lock = new ReentrantLock();static Condition waitBreadQueue = lock.newCondition();static Condition waitCoffeeQueue = lock.newCondition();static volatile boolean hasBread = false;static volatile boolean hasCoffee = false;public static void main(String[] args) throws InterruptedException {new Thread(() -> {try {lock.lock();while (!hasCoffee) {try {log.debug("等待咖啡");waitCoffeeQueue.await(); // 等咖啡隊列} catch (InterruptedException e) {e.printStackTrace();}}log.debug("t1 收到咖啡");} finally {log.debug("t1 釋放鎖");lock.unlock();}}, "t1").start();new Thread(() -> {try {lock.lock();while (!hasBread) {try {log.debug("等待面包");waitBreadQueue.await(); // 等面包隊列} catch (InterruptedException e) {e.printStackTrace();}}log.debug("t2 收到面包");} finally {log.debug("t2 釋放鎖");lock.unlock();}}, "t2").start();sleep(1000);handleCoffee();sleep(1000);handleBread();}public static void handleCoffee() {lock.lock();try {log.debug("發咖啡");hasCoffee = true;waitCoffeeQueue.signal(); // 喚醒等待咖啡的線程} finally {lock.unlock();}}public static void handleBread() {lock.lock();try {log.debug("發面包");hasBread = true;waitBreadQueue.signal(); // 喚醒等待面包的線程} finally {lock.unlock();}}
}?
如有錯誤,歡迎指正!!!?
圖源來自網絡,侵刪!