摘要:實現死鎖的方法有兩種,一種是使用同步代碼塊,另一種是使用重入鎖。但是如果調用帶超時的方法,那么如果線程在等待時被中斷,將拋出一個異常,這是一個非常有用的特性,因為它允許程序打破死鎖。
思路:
死鎖是指在多線程環境下的這么一種場景,兩個(多個)線程在分別拿到自己的鎖時嘗試獲取對方的鎖,由于必須等待對方釋放鎖才能獲取,然而雙方誰也不肯先釋放自己的鎖, 導致雙方誰都無法繼續執行。
通過一個實現runnable接口的類實例作為兩個線程的執行對象,在該類中有兩個Object的靜態變量作為鎖.通過該類的一個開關變量實現在同一個run方法中執行兩段不同的邏輯,一個先獲取鎖1, 再獲取鎖2,另一個分支則剛好相反。
為了使第一個執行的線程在拿到第二個鎖之前失去cpu執行權,方便構造死鎖場景,在嘗試獲取第二個鎖之前,讓線程休眠一段時間,因為sleep()方法不會釋放鎖。
實現死鎖的方法有兩種,一種是使用synchronized同步代碼塊,另一種是使用reentrantlock重入鎖。
使用同步代碼塊實現死鎖
代碼
public class TestDeadLock implements Runnable {
//開關 private boolean flag; //鎖1 private static Object lock1 = new Object(); //鎖2 private static Object lock2 = new Object(); public TestDeadLock(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { synchronized (lock1) { System.out.println(flag + "線程拿到了lock1"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println(flag + "線程拿到了lock2"); } } } else { synchronized (lock2) { System.out.println(flag + "線程拿到了lock2"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println(flag + "線程拿到了lock1"); } } } } public static void main(String[] args) { Thread thread1 = new Thread(new TestDeadLock(true)); Thread thread2 = new Thread(new TestDeadLock(false)); thread1.start(); thread2.start(); }
}
運行結果
true線程拿到了lock1
false線程拿到了lock2
使用ReentrantLock實現死鎖
代碼
public class TestDeadLock2 implements Runnable{
private boolean flag; private static ReentrantLock lock1=new ReentrantLock(); private static ReentrantLock lock2=new ReentrantLock(); public TestDeadLock2(boolean flag) { this.flag = flag; } @Override public void run() { try { if(flag){ lock1.lock(); System.out.println(flag + "線程獲取了Lock1"); TimeUnit.SECONDS.sleep(1); lock2.lock(); System.out.println(flag+"線程獲取了Lock2"); }else{ lock2.lock(); System.out.println(flag + "線程獲取了Lock2"); TimeUnit.SECONDS.sleep(1); lock1.lock(); System.out.println(flag+"線程獲取了Lock1"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if(lock1.isHeldByCurrentThread()){ lock1.unlock(); } if(lock2.isHeldByCurrentThread()){ lock2.unlock(); } } } public static void main(String[] args) throws InterruptedException { Thread thread1=new Thread(new TestDeadLock2(true)); Thread thread2=new Thread(new TestDeadLock2(false)); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("主線程已結束"); }
}
運行結果
false線程獲取了Lock2
true線程獲取了Lock1
ReentrantLock和Synchronized的區別,具體可見
Java中的ReentrantLock和synchronized兩種鎖定機制的對比
。總的來說,ReentrantLock所提供的功能比Synchronized要豐富的多,比如
lockInterruptibly
API簽名
public void lockInterruptibly() throws InterruptedException
代碼
public class TestDeadLock3 implements Runnable {
private boolean flag; static ReentrantLock lock1 = new ReentrantLock(); static ReentrantLock lock2 = new ReentrantLock(); public TestDeadLock3(boolean flag) { this.flag = flag; } @Override public void run() { try { if (flag) { //可中斷地加鎖 lock1.lockInterruptibly(); System.out.println(flag + "線程獲取了lock1"); TimeUnit.SECONDS.sleep(1); lock2.lockInterruptibly(); System.out.println(flag + "線程獲取了lock2"); } else { lock2.lockInterruptibly(); System.out.println(flag + "線程獲取lock2"); TimeUnit.SECONDS.sleep(1); lock1.lockInterruptibly(); System.out.println(flag + "線程獲取了lock1"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock1.isHeldByCurrentThread()) { lock1.unlock(); System.out.println(flag + "線程釋放lock1鎖"); } if (lock2.isHeldByCurrentThread()) { lock2.unlock(); System.out.println(flag + "線程釋放lock2鎖"); } System.out.println(flag + "線程已退出"); } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new TestDeadLock3(true)); Thread thread2 = new Thread(new TestDeadLock3(false)); thread1.start(); thread2.start(); //主線程休眠5秒 TimeUnit.SECONDS.sleep(5); thread1.interrupt(); }
}
運行結果
true線程獲取了lock1
false線程獲取lock2
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
true線程釋放lock1鎖
at com.akane.test.reentrantlock.TestDeadLock3.run(TestDeadLock3.java:31)
true線程已退出
at java.lang.Thread.run(Thread.java:744)
false線程獲取了lock1
false線程釋放lock1鎖
false線程釋放lock2鎖
false線程已退出
Process finished with exit code 0
關于interrupt的用法
synchronized在獲鎖的過程中是不能被中斷的,意思是說如果產生了死鎖,則不可能被中斷(請參考后面的測試例子)。與synchronized功能相似的reentrantLock.lock()方法也是一樣,它也不可中斷的,即如果發生死鎖,那么reentrantLock.lock()方法無法終止,如果調用時被阻塞,則它一直阻塞到它獲取到鎖為止。但是如果調用帶超時的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果線程在等待時被中斷,將拋出一個InterruptedException異常,這是一個非常有用的特性,因為它允許程序打破死鎖。你也可以調用reentrantLock.lockInterruptibly()方法,它就相當于一個超時設為無限的tryLock方法
主線程對Thread1進行了中斷,thread1拋出異常,異常被捕獲,在finally中釋放thread1獲得的鎖,線程2獲得需要的鎖,該線程得以繼續執行,死鎖就被解決了
tryLock
當然,ReentrantLock還提供了另外一個更好的方法解決死鎖問題,那就是使用tryLock()方法,該方法會嘗試獲得鎖,如果成功,返回true,失敗則返回false。該方法不等待或等待一段時間就返回。
API簽名
public boolean tryLock() 立即返回
public boolean tryLock(long timeout, TimeUnit unit) 等待一段時間后返回
死鎖的原因在于吃著碗里的看著鍋里的,我們讓線程拿到一個鎖之后無論是否拿到第二個鎖,都釋放已經拿到的鎖,可以將此邏輯放入finally中,配合外層的while(true)多次重復嘗試,如果成功獲取兩個鎖,則釋放兩個鎖的同時推出while循環,以下是代碼實現,線程睡眠時間由1秒改為1毫秒,減少測試需要的時間
代碼
public class TestDeadLock4 implements Runnable{
private boolean flag; static ReentrantLock lock1 = new ReentrantLock(); static ReentrantLock lock2 = new ReentrantLock(); //統計發生死鎖的次數 private static int count; public TestDeadLock4(boolean flag) { this.flag = flag; } @Override public void run() { if(flag){ while (true) { if(lock1.tryLock()){ System.out.println(flag+"線程獲得了lock1"); try { TimeUnit.MILLISECONDS.sleep(1); try { if(lock2.tryLock()){ System.out.println(flag+"獲得了lock2"); } } finally { //同時獲得Lock1和lock2,沒有發生死鎖,任務完成,退出循環 if(lock1.isHeldByCurrentThread()&&lock2.isHeldByCurrentThread()){ System.out.println(flag+"線程執行完畢"+"---------------------"); lock1.unlock(); lock2.unlock(); break; }else{ //說明發生了死鎖,只需要釋放lock1 count++; System.out.println("發生了"+count+"次死鎖"); lock1.unlock(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } }else{ while (true) { if(lock2.tryLock()){ System.out.println(flag+"線程獲得了lock2"); try { TimeUnit.MILLISECONDS.sleep(1); try { if(lock1.tryLock()){ System.out.println(flag+"線程獲得了lock1"); } } finally { if(lock1.isHeldByCurrentThread()&&lock2.isHeldByCurrentThread()){ System.out.println(flag+"線程執行完畢"+"---------------------"); lock1.unlock(); lock2.unlock(); break; }else{ count++; System.out.println("發生了"+count+"次死鎖"); lock2.unlock(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new TestDeadLock4(true)); Thread thread2 = new Thread(new TestDeadLock4(false)); thread1.start(); thread2.start(); }
}
運行結果(部分)
全選復制放進筆記true線程獲得了lock1
false線程獲得了lock2
發生了3358次死鎖
false獲得了lock1
false線程執行完畢---------------------
true線程獲得了lock1
true獲得了lock2
true線程執行完畢---------------------
Process finished with exit code 0
公平鎖
除此之外,ReentrantLock還有能實現線程公平獲取鎖的功能,所謂的公平,指的是在申請獲取鎖的隊列中,排在前面的線程總是優先獲得需要的鎖,Synchronized同步獲得鎖的方式是非公平的,舉個例子,線程A和B都嘗試獲得C持有的鎖,當C釋放該鎖時,A和B誰能獲得該鎖是不確定的,也就是非公平的,而ReentrantLock提供公平地,即先來后到地獲取鎖的方式。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65534.html
摘要:的鎖是非公平鎖,默認情況下也是非公平鎖,但可以通過帶布爾值的構造函數要求使用公平鎖。有序性,是保證線程內串行語義,避免指令重排等。公平性是減少線程饑餓個別線程長期等待鎖,但始終無法獲取情況發生的一個辦法。 目錄介紹 1.Synchronize和ReentrantLock區別 1.1 相似點 1.2 區別 1.3 什么是線程安全問題?如何理解 1.4 線程安全需要保證幾個基本特性 ...
摘要:線程啟動規則對象的方法先行發生于此線程的每一個動作。所以局部變量是不被多個線程所共享的,也就不會出現并發問題。通過獲取到數據,放入當前線程處理完之后將當前線程中的信息移除。主線程必須在啟動其他線程后立即調用方法。 一、線程安全性 定義:當多個線程訪問某個類時,不管運行時環境采用何種調度方式,或者這些線程將如何交替執行,并且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行...
摘要:但是單核我們還是要應用多線程,就是為了防止阻塞。多線程可以防止這個問題,多條線程同時運行,哪怕一條線程的代碼執行讀取數據阻塞,也不會影響其它任務的執行。 1、多線程有什么用?一個可能在很多人看來很扯淡的一個問題:我會用多線程就好了,還管它有什么用?在我看來,這個回答更扯淡。所謂知其然知其所以然,會用只是知其然,為什么用才是知其所以然,只有達到知其然知其所以然的程度才可以說是把一個知識點...
摘要:近段時間在準備實習的面試,在網上看到一份面試題,就慢慢試著做,爭取每天積累一點點。現在每天給自己在面試題編寫的任務是題,有時候忙起來可能就沒有時間寫了,但是爭取日更,即使當天沒更也會在之后的更新補上。 ????近段時間在準備實習的面試,在網上看到一份面試題,就慢慢試著做,爭取每天積累一點點。????暫時手頭上的面試題只有一份,題量還是挺大的,有208題,所以可能講的不是很詳細,只是我自...
閱讀 1714·2021-11-22 15:33
閱讀 2085·2021-10-08 10:04
閱讀 3543·2021-08-27 13:12
閱讀 3419·2019-08-30 13:06
閱讀 1467·2019-08-29 16:43
閱讀 1392·2019-08-29 16:40
閱讀 786·2019-08-29 16:15
閱讀 2746·2019-08-29 14:13