文章目錄
- 💐synchrosized的可重入特性
- 關于死鎖:
- 哲學家就餐問題
- 💡如何避免/解決死鎖
💐synchrosized的可重入特性
可重入特性:當一個線程針對一個對象同時加鎖多次,不會構成死鎖,這樣的特性稱為可重入性
例如下圖:
為了防止上述死鎖情況,synchrosized 就引入了可重入性解決;
線程在加鎖時,在這個鎖對象內部,它會記錄是對哪個線程加了鎖,當對同一個線程再次進行加鎖時,就會判斷該線程是不是同一個線程并且是否已經持有了鎖,如果已經有了鎖,那么也會重復進行加鎖,不會導致死鎖現象;
**那么,問題就來了,如果加兩次鎖,在 }2 的地方是否應該解鎖呢?**答案:不能釋放鎖
如果加了n次鎖呢?該怎么去釋放呢?
答案:在鎖對象中,不僅會記錄對哪個線程加了鎖,還會有一個計數器記錄加鎖的次數;如果對同一個線程加鎖多次,那么每當執行完一個加鎖的代碼塊時,計數器就會減1,一直到最后一個鎖時,才會釋放鎖;
關于死鎖:
-
在Java中,如果一個線程對同一個鎖連續加鎖兩次,不會造成死鎖現象
-
如果兩個線程,兩把鎖,每個線程都嵌套的加兩個不同的鎖,就會造成死鎖現象
例如:讓線程1先獲取 lock1,線程2獲取 lock2,然后在 thread1 的內部再嘗試獲取 lock2,在 thread2 的內部再嘗試獲取 lock1
public static void main(String[] args) {//定義兩把鎖Object lock1 = new Object();Object lock2 = new Object();//讓線程1嵌套獲取兩把鎖Thread thread1 = new Thread(() -> {synchronized (lock1) {//此處睡眠很重要,如果沒有睡眠,線程1可能就會一下子把兩把鎖都獲取了,就構不成死鎖現象了try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lock2) {System.out.println("thread1加鎖成功");}}});//讓線程2嵌套獲取兩把鎖Thread thread2 = new Thread(() -> {synchronized (lock2) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lock1) {System.out.println("thread2加鎖成功");}}});thread1.start();thread2.start();}
執行結果:
3. n個線程,m把鎖,也容易出現死鎖問題,例如哲學家就餐問題:
哲學家就餐問題
死鎖 是一個比較嚴重的bug,那如何避免/解決死鎖呢?
💡如何避免/解決死鎖
要想避免死鎖,就要先知道死鎖是怎么形成的,這樣才能對癥下藥,導致死鎖的四個必要條件:
1.互斥使用:當線程1獲取鎖之后,線程2也想獲取同一把鎖,就會阻塞等待(鎖的特性)
2.不可搶占:當線程1已經獲取到鎖之后,線程2不能強行搶占鎖(鎖的特性)
3.請求保持:一個線程嘗試獲取多把鎖(一個線程獲取到鎖1之后,還想嘗試獲取鎖2,此時鎖1也并未解鎖)例如上面的嵌套加鎖代碼
4.循環等待:線程獲取鎖時,形成了環路;例如,上面哲學家同時拿起左邊的筷子
第一點和第二點是鎖的特性,如果想要解決死鎖,就要破壞第三點和第四點,
對于第三點來講,只要避免兩把不同的鎖嵌套獲取即可
對于第四點來講,可以約定給所有的鎖進行一個編號,規定所有的線程只能按順序先獲取編號小的鎖,然后獲取編號大的,例如:
以上雖然時嵌套加鎖的,但是并未形成環路,得到lock1鎖的線程執行,未獲得lock1的線程阻塞等待,并且也無法獲得lock2