Java并發編程2(鎖-Sychronized)

目錄

認識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)是指兩個或更多線程在執行過程中,由于爭奪資源而造成的一種互相等待的狀態。在這種狀態下,沒有任何線程能夠繼續前進,整個系統或部分系統因此陷入停滯。死鎖是并發編程中常見的問題之一,尤其是在多線程環境下對共享資源進行訪問時。

    死鎖發生的必要條件

    1. 互斥條件:至少有一個資源必須處于非共享模式,即只能被一個線程占用。例如,文件寫入權限通常就是互斥的。
    2. 占有并等待條件:一個線程已經占有了至少一個資源,并且正在等待其他線程占有的資源。也就是說,該線程不會釋放自己已獲得的資源直到它獲得了所有需要的資源
    3. 不可剝奪條件:資源不能被強制從某個線程中搶走,只能由擁有它的線程主動釋放。這意味著一旦一個線程獲得了某個資源,它可以在不自愿的情況下保持對該資源的控制權,直到它完成任務并釋放資源。
    4. 循環等待條件:存在一組等待資源的線程鏈,其中每個線程都在等待下一個線程當前占有的資源。例如,線程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時間或其他必要的資源來推進其任務

    饑餓的原因

    1. 資源競爭:當多個線程競爭有限的資源時,如果沒有公平的調度機制,某些線程可能會始終得不到資源,從而導致饑餓。例如,如果系統總是優先處理高優先級的任務,那么低優先級的任務可能永遠得不到執行的機會。
    2. 不恰當的鎖機制:在使用鎖進行同步控制時,如果某個線程長時間持有鎖而不釋放,其他等待該鎖的線程就會被阻塞,可能導致這些線程出現饑餓現象。
    3. 忙等待(Busy-waiting):如果一個線程通過忙等待的方式來等待某個條件成立,而這個條件很少或幾乎不會成立,那么這個線程就會一直消耗CPU資源,同時無法完成實際的工作。
    4. 不公平的調度器:操作系統或運行時環境中的線程調度器如果沒有實現公平調度算法,也可能導致某些線程長期得不到執行機會。?

    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();}}
    }?


    如有錯誤,歡迎指正!!!?

    圖源來自網絡,侵刪!

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/news/899134.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/news/899134.shtml
    英文地址,請注明出處:http://en.pswp.cn/news/899134.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    現階段高校的人工智能方案培訓如何?

    人工智能在未來肯定是核心發展力&#xff0c;核心競爭力&#xff0c;也是國家重點扶持的對象&#xff0c;但我還是不看好高校的人工智能方向&#xff0c;只是怕有些同學對市場前景盲目樂觀&#xff0c;就輕易上車了。 你要是985以上的高校&#xff0c;可以考慮選擇人工智能&…

    JavaScript中的繼承有哪些方式?各有什么優缺點

    在 JavaScript 中&#xff0c;繼承主要通過原型鏈實現&#xff0c;常見的繼承方式有以下幾種&#xff0c;每種方式都有其優缺點&#xff1a; 1. 原型鏈繼承 1. 實現方式&#xff1a;將子類的原型對象指向父類的實例。 function Parent() {} function Child() {} Child.protot…

    深入理解指針(3)(C語言版)

    文章目錄 前言 一、字符指針變量二、數組指針變量2.1 數組指針變量是什么2.2 數組指針變量怎么初始化2.2.1 靜態初始化2.2.2 動態初始化 三、二維數組傳參的本質四、函數指針變量4.1 函數指針變量的創建4.2 函數指針變量的使用4.3 typedef關鍵字4.4拓展 五、函數指針數組六、轉…

    Linux之 權限提升(Linux Privilege Escalation)

    Linux 之權限提升 系統信息 1.獲取操作系統信息 2.檢查PATH&#xff0c;是否有任何可寫的文件夾&#xff1f; 3.檢查環境變量&#xff0c;有任何敏感細節嗎&#xff1f; 4.使用腳本&#xff08;DirtyCow&#xff1f;&#xff09;搜索內核漏洞 5.檢查sudo 版本是否存在漏洞…

    【leetcode hot 100 215】數組中的第K個最大元素

    解法一&#xff1a;維護最大最小值 -> 堆 -> k個元素的最小值堆 class Solution {public int findKthLargest(int[] nums, int k) {// 維護最大最小值 -> 堆 -> k個元素的最小值堆PriorityQueue<Integer> heap new PriorityQueue<>((n1, n2) -> n…

    csp信奧賽C++常用的數學函數詳解

    csp信奧賽C常用的數學函數詳解 在信息學奧林匹克競賽&#xff08;信奧賽&#xff09;中&#xff0c;C 的 <cmath> 頭文件提供了豐富的數學函數&#xff0c;用于高效處理數學運算。以下是常用系統數學函數的詳細講解及匯總表格。 絕對值函數 int abs(int x)&#xff1a;返…

    Java IntelliJ IDEA 中配置多個 JDK 版本

    目錄 一、添加多個 JDK 版本1. 下載并安裝多個 JDK 版本2. 配置 JDK 在 IntelliJ IDEA 中 二、在項目中切換 JDK 版本1. 設置項目使用的 JDK 版本2. 設置模塊使用的 JDK 版本 三、在運行配置中指定 JDK 版本四、總結 在實際開發中&#xff0c;我們常常需要在同一個項目中使用不…

    ChatDBA VS DeepSeek:快速診斷 OceanBase 集群新租戶數據同步異常

    社區王牌專欄《一問一實驗&#xff1a;AI 版》改版以來已發布多期&#xff08;51-60&#xff09;&#xff0c;展現了 ChatDBA 在多種場景下解決問題的效果。 下面讓我們正式進入《一問一實驗&#xff1a;AI 版》第 62 期&#xff0c;看看 ChatDBA 最新效果以及與熱門大模型 De…

    Java條碼與二維碼生成技術詳解

    一、技術選型分析 1.1 條碼生成方案 Barbecue是最成熟的Java條碼庫&#xff0c;支持&#xff1a; Code 128EAN-13/UPC-AUSPS Inteligent Mail等12種工業標準格式 1.2 二維碼方案對比 庫名稱維護狀態復雜度功能擴展性ZXing★★★★☆較高強QRGen★★★☆☆簡單一般BoofCV★…

    air780eq 阿里云

    硬件&#xff1a;APM32F030C8 Air 780eq 參考文檔&#xff1a; 合宙780E-4G模塊通過AT指令連接到阿里云平臺&#xff0c;實現信息的收發_air780e上傳阿里云屬性值at命令-CSDN博客 阿里云 - atair780eq - 合宙文檔中心 4G模塊接入阿里云-實現數據上傳和命令下發_4g模塊上傳…

    oracle數據庫(數據庫啟動關閉/sqlplus登錄及基本操作/設置字符集/distinct去重)

    目錄 1. Oracle數據庫啟動 2. Oracle數據庫關閉 3. sqlplus登錄Oracle數據庫 3.1 使用sqlplus登錄Oracle數據庫 3.2 使用sqlplus登錄Oracle數據庫 3.3 遠程登錄 3.4 解鎖用戶 3.5 修改用戶密碼 3.6 查看當前語言環境 4. sqlplus基本操作 4.1 顯示當前用戶 4.2 查看當前用戶…

    Java 大視界 -- Java 大數據在智能金融區塊鏈跨境支付與結算中的應用(154)

    &#x1f496;親愛的朋友們&#xff0c;熱烈歡迎來到 青云交的博客&#xff01;能與諸位在此相逢&#xff0c;我倍感榮幸。在這飛速更迭的時代&#xff0c;我們都渴望一方心靈凈土&#xff0c;而 我的博客 正是這樣溫暖的所在。這里為你呈上趣味與實用兼具的知識&#xff0c;也…

    大模型詞表注入

    大模型詞表注入&#xff08;Vocabulary Injection&#xff09; 大模型詞表注入&#xff08;Vocabulary Injection&#xff09;是指在預訓練語言模型&#xff08;如GPT、LLAMA等&#xff09;的基礎上&#xff0c;動態擴展其詞表&#xff08;Vocabulary&#xff09;的技術&#…

    在Cesium中使用ThreeJs材質(不是場景融合哦)

    在Cesium中使用ThreeJs材質(不是場景融合哦&#xff09;_嗶哩嗶哩_bilibili

    初教六雙機一飛沖天動作要領

    初教六雙機一飛沖天動作要領 初教六雙機“一飛沖天”是典型的垂直爬升特技動作&#xff0c;要求雙機以近乎垂直的姿態同步高速爬升&#xff0c;展現飛機的動力性能與編隊協同能力。以下是該動作的詳細技術解析與執行要點&#xff1a; 一、動作定義與特點 基本形態 雙機以相同速…

    給Web開發者的HarmonyOS指南02-布局樣式

    給Web開發者的HarmonyOS指南02-布局樣式 本系列教程適合鴻蒙 HarmonyOS 初學者&#xff0c;為那些熟悉用 HTML 與 CSS 語法的 Web 前端開發者準備的。 本系列教程會將 HTML/CSS 代碼片段替換為等價的 HarmonyOS/ArkUI 代碼。 布局基礎對比 在Web開發中&#xff0c;我們使用CS…

    京東軟件測試崗位經典面試題(附答案)

    1、黑盒測試的測試用例常見設計方法都有哪些&#xff1f;請分別以具體的例子來說明這些方法在測試用例設計工作中的應用。 1&#xff09;等價類劃分&#xff1a;等價類是指某個輸入域的子集合.在該子集合中&#xff0c;各個輸入數據對于揭露程序中的錯誤都是等效的.并合理地假…

    3.26[a]paracompute homework

    5555 負載不平衡指多個線程的計算量差異顯著&#xff0c;導致部分線程空轉或等待&#xff0c;降低并行效率。其核心矛盾在于任務劃分的靜態性與計算動態性不匹配&#xff0c;尤其在處理不規則數據或動態任務時尤為突出。以稀疏矩陣的向量乘法為例&#xff0c;假設其非零元素分…

    網站安全專欄-------淺談CC攻擊和DDoS攻擊的區別

    CC攻擊和DDoS攻擊都是網絡攻擊的類型&#xff0c;但它們在攻擊方式、目標和效果上有所不同。以下是它們之間的一些主要區別&#xff1a; ### 1. 定義 - **DDoS攻擊&#xff08;分布式拒絕服務攻擊&#xff09;**&#xff1a; DDoS攻擊是指攻擊者通過大量的分布式計算機&#x…

    帕金森患者的生活重塑:從 “嘴” 開啟康復之旅

    當提到帕金森病&#xff0c;許多人會聯想到震顫、僵硬和行動遲緩等癥狀。這種神經系統退行性疾病&#xff0c;給患者的生活帶來了巨大的挑戰。然而&#xff0c;你可知道&#xff0c;帕金森患者恢復正常生活&#xff0c;可以從 “嘴” 開始管理&#xff1f; 帕金森病在全球影響著…