摘要:實(shí)現(xiàn)死鎖的方法有兩種,一種是使用同步代碼塊,另一種是使用重入鎖。但是如果調(diào)用帶超時(shí)的方法,那么如果線程在等待時(shí)被中斷,將拋出一個(gè)異常,這是一個(gè)非常有用的特性,因?yàn)樗试S程序打破死鎖。
思路:
死鎖是指在多線程環(huán)境下的這么一種場(chǎng)景,兩個(gè)(多個(gè))線程在分別拿到自己的鎖時(shí)嘗試獲取對(duì)方的鎖,由于必須等待對(duì)方釋放鎖才能獲取,然而雙方誰(shuí)也不肯先釋放自己的鎖, 導(dǎo)致雙方誰(shuí)都無(wú)法繼續(xù)執(zhí)行。
通過(guò)一個(gè)實(shí)現(xiàn)runnable接口的類實(shí)例作為兩個(gè)線程的執(zhí)行對(duì)象,在該類中有兩個(gè)Object的靜態(tài)變量作為鎖.通過(guò)該類的一個(gè)開(kāi)關(guān)變量實(shí)現(xiàn)在同一個(gè)run方法中執(zhí)行兩段不同的邏輯,一個(gè)先獲取鎖1, 再獲取鎖2,另一個(gè)分支則剛好相反。
為了使第一個(gè)執(zhí)行的線程在拿到第二個(gè)鎖之前失去cpu執(zhí)行權(quán),方便構(gòu)造死鎖場(chǎng)景,在嘗試獲取第二個(gè)鎖之前,讓線程休眠一段時(shí)間,因?yàn)閟leep()方法不會(huì)釋放鎖。
實(shí)現(xiàn)死鎖的方法有兩種,一種是使用synchronized同步代碼塊,另一種是使用reentrantlock重入鎖。
使用同步代碼塊實(shí)現(xiàn)死鎖
代碼
public class TestDeadLock implements Runnable {
//開(kāi)關(guān) 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(); }
}
運(yùn)行結(jié)果
true線程拿到了lock1
false線程拿到了lock2
使用ReentrantLock實(shí)現(xiàn)死鎖
代碼
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("主線程已結(jié)束"); }
}
運(yùn)行結(jié)果
false線程獲取了Lock2
true線程獲取了Lock1
ReentrantLock和Synchronized的區(qū)別,具體可見(jiàn)
Java中的ReentrantLock和synchronized兩種鎖定機(jī)制的對(duì)比
。總的來(lái)說(shuō),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(); }
}
運(yùn)行結(jié)果
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
關(guān)于interrupt的用法
synchronized在獲鎖的過(guò)程中是不能被中斷的,意思是說(shuō)如果產(chǎn)生了死鎖,則不可能被中斷(請(qǐng)參考后面的測(cè)試?yán)樱?。與synchronized功能相似的reentrantLock.lock()方法也是一樣,它也不可中斷的,即如果發(fā)生死鎖,那么reentrantLock.lock()方法無(wú)法終止,如果調(diào)用時(shí)被阻塞,則它一直阻塞到它獲取到鎖為止。但是如果調(diào)用帶超時(shí)的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果線程在等待時(shí)被中斷,將拋出一個(gè)InterruptedException異常,這是一個(gè)非常有用的特性,因?yàn)樗试S程序打破死鎖。你也可以調(diào)用reentrantLock.lockInterruptibly()方法,它就相當(dāng)于一個(gè)超時(shí)設(shè)為無(wú)限的tryLock方法
主線程對(duì)Thread1進(jìn)行了中斷,thread1拋出異常,異常被捕獲,在finally中釋放thread1獲得的鎖,線程2獲得需要的鎖,該線程得以繼續(xù)執(zhí)行,死鎖就被解決了
tryLock
當(dāng)然,ReentrantLock還提供了另外一個(gè)更好的方法解決死鎖問(wèn)題,那就是使用tryLock()方法,該方法會(huì)嘗試獲得鎖,如果成功,返回true,失敗則返回false。該方法不等待或等待一段時(shí)間就返回。
API簽名
public boolean tryLock() 立即返回
public boolean tryLock(long timeout, TimeUnit unit) 等待一段時(shí)間后返回
死鎖的原因在于吃著碗里的看著鍋里的,我們讓線程拿到一個(gè)鎖之后無(wú)論是否拿到第二個(gè)鎖,都釋放已經(jīng)拿到的鎖,可以將此邏輯放入finally中,配合外層的while(true)多次重復(fù)嘗試,如果成功獲取兩個(gè)鎖,則釋放兩個(gè)鎖的同時(shí)推出while循環(huán),以下是代碼實(shí)現(xiàn),線程睡眠時(shí)間由1秒改為1毫秒,減少測(cè)試需要的時(shí)間
代碼
public class TestDeadLock4 implements Runnable{
private boolean flag; static ReentrantLock lock1 = new ReentrantLock(); static ReentrantLock lock2 = new ReentrantLock(); //統(tǒng)計(jì)發(fā)生死鎖的次數(shù) 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 { //同時(shí)獲得Lock1和lock2,沒(méi)有發(fā)生死鎖,任務(wù)完成,退出循環(huán) if(lock1.isHeldByCurrentThread()&&lock2.isHeldByCurrentThread()){ System.out.println(flag+"線程執(zhí)行完畢"+"---------------------"); lock1.unlock(); lock2.unlock(); break; }else{ //說(shuō)明發(fā)生了死鎖,只需要釋放lock1 count++; System.out.println("發(fā)生了"+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+"線程執(zhí)行完畢"+"---------------------"); lock1.unlock(); lock2.unlock(); break; }else{ count++; System.out.println("發(fā)生了"+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(); }
}
運(yùn)行結(jié)果(部分)
全選復(fù)制放進(jìn)筆記true線程獲得了lock1
false線程獲得了lock2
發(fā)生了3358次死鎖
false獲得了lock1
false線程執(zhí)行完畢---------------------
true線程獲得了lock1
true獲得了lock2
true線程執(zhí)行完畢---------------------
Process finished with exit code 0
公平鎖
除此之外,ReentrantLock還有能實(shí)現(xiàn)線程公平獲取鎖的功能,所謂的公平,指的是在申請(qǐng)獲取鎖的隊(duì)列中,排在前面的線程總是優(yōu)先獲得需要的鎖,Synchronized同步獲得鎖的方式是非公平的,舉個(gè)例子,線程A和B都嘗試獲得C持有的鎖,當(dāng)C釋放該鎖時(shí),A和B誰(shuí)能獲得該鎖是不確定的,也就是非公平的,而ReentrantLock提供公平地,即先來(lái)后到地獲取鎖的方式。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65534.html
摘要:的鎖是非公平鎖,默認(rèn)情況下也是非公平鎖,但可以通過(guò)帶布爾值的構(gòu)造函數(shù)要求使用公平鎖。有序性,是保證線程內(nèi)串行語(yǔ)義,避免指令重排等。公平性是減少線程饑餓個(gè)別線程長(zhǎng)期等待鎖,但始終無(wú)法獲取情況發(fā)生的一個(gè)辦法。 目錄介紹 1.Synchronize和ReentrantLock區(qū)別 1.1 相似點(diǎn) 1.2 區(qū)別 1.3 什么是線程安全問(wèn)題?如何理解 1.4 線程安全需要保證幾個(gè)基本特性 ...
摘要:線程啟動(dòng)規(guī)則對(duì)象的方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。所以局部變量是不被多個(gè)線程所共享的,也就不會(huì)出現(xiàn)并發(fā)問(wèn)題。通過(guò)獲取到數(shù)據(jù),放入當(dāng)前線程處理完之后將當(dāng)前線程中的信息移除。主線程必須在啟動(dòng)其他線程后立即調(diào)用方法。 一、線程安全性 定義:當(dāng)多個(gè)線程訪問(wèn)某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式,或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類都能表現(xiàn)出正確的行...
摘要:如果同一個(gè)線程再次請(qǐng)求該鎖,計(jì)數(shù)器會(huì)遞增,每次占有的線程退出同步代碼塊時(shí)計(jì)數(shù)器會(huì)遞減,直至減為時(shí)鎖才會(huì)被釋放。表示或在該上的所有線程的個(gè)數(shù)用來(lái)實(shí)現(xiàn)重入鎖的計(jì)數(shù)。只有兩種可能的值表示沒(méi)有需要喚醒的線程表示要喚醒一個(gè)繼任線程來(lái)競(jìng)爭(zhēng)鎖。 一、synchronized 1.類型 (1)對(duì)象鎖 對(duì)象鎖是作用在實(shí)例方法或者一個(gè)對(duì)象實(shí)例上面的 一個(gè)類可以有多個(gè)實(shí)例對(duì)象,因此一個(gè)類的對(duì)象鎖可能會(huì)有多個(gè)...
摘要:但是單核我們還是要應(yīng)用多線程,就是為了防止阻塞。多線程可以防止這個(gè)問(wèn)題,多條線程同時(shí)運(yùn)行,哪怕一條線程的代碼執(zhí)行讀取數(shù)據(jù)阻塞,也不會(huì)影響其它任務(wù)的執(zhí)行。 1、多線程有什么用?一個(gè)可能在很多人看來(lái)很扯淡的一個(gè)問(wèn)題:我會(huì)用多線程就好了,還管它有什么用?在我看來(lái),這個(gè)回答更扯淡。所謂知其然知其所以然,會(huì)用只是知其然,為什么用才是知其所以然,只有達(dá)到知其然知其所以然的程度才可以說(shuō)是把一個(gè)知識(shí)點(diǎn)...
摘要:近段時(shí)間在準(zhǔn)備實(shí)習(xí)的面試,在網(wǎng)上看到一份面試題,就慢慢試著做,爭(zhēng)取每天積累一點(diǎn)點(diǎn)。現(xiàn)在每天給自己在面試題編寫(xiě)的任務(wù)是題,有時(shí)候忙起來(lái)可能就沒(méi)有時(shí)間寫(xiě)了,但是爭(zhēng)取日更,即使當(dāng)天沒(méi)更也會(huì)在之后的更新補(bǔ)上。 ????近段時(shí)間在準(zhǔn)備實(shí)習(xí)的面試,在網(wǎng)上看到一份面試題,就慢慢試著做,爭(zhēng)取每天積累一點(diǎn)點(diǎn)。????暫時(shí)手頭上的面試題只有一份,題量還是挺大的,有208題,所以可能講的不是很詳細(xì),只是我自...
閱讀 1726·2021-11-22 15:33
閱讀 2102·2021-10-08 10:04
閱讀 3555·2021-08-27 13:12
閱讀 3429·2019-08-30 13:06
閱讀 1477·2019-08-29 16:43
閱讀 1400·2019-08-29 16:40
閱讀 795·2019-08-29 16:15
閱讀 2752·2019-08-29 14:13