- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- 如何解決Java多線(xiàn)程死鎖問(wèn)題
多線(xiàn)程編程中,因為搶占資源造成了線(xiàn)程無(wú)限等待的情況,此情況稱(chēng)為死鎖
。
注意:線(xiàn)程和鎖的關(guān)系是:一個(gè)線(xiàn)程可以擁有多把鎖,一個(gè)鎖只能被一個(gè)線(xiàn)程擁有。
當兩個(gè)線(xiàn)程分別擁有一把各自的鎖之后,又嘗試去獲取對方的鎖,這樣就會(huì )導致死鎖情況的發(fā)生,具體先看下面代碼:
/** * 線(xiàn)程死鎖問(wèn)題 */ public class DeadLock { public static void main(String[] args) { //創(chuàng )建兩個(gè)鎖對象 Object lock1 = new Object(); Object lock2 = new Object(); //創(chuàng )建子線(xiàn)程 /* 線(xiàn)程1:①先獲得鎖1 ②休眠1s,讓線(xiàn)程2獲得鎖2 ③線(xiàn)程1嘗試獲取鎖2 線(xiàn)程2同理 */ Thread thread1 = new Thread(new Runnable() { @Override public void run() { //線(xiàn)程1業(yè)務(wù)邏輯 synchronized(lock1){ System.out.println("線(xiàn)程1得到了鎖子1"); try { //休眠1s,讓線(xiàn)程2先得到鎖2 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線(xiàn)程1嘗試獲取鎖2..."); synchronized(lock2){ System.out.println("線(xiàn)程1獲得了鎖2!"); } } } },"線(xiàn)程1"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { //線(xiàn)程2業(yè)務(wù)邏輯 synchronized(lock2){ System.out.println("線(xiàn)程2得到了鎖子2"); try { //休眠1s,讓線(xiàn)程1先得到鎖1;因為線(xiàn)程是并發(fā)執行我們不知道誰(shuí)先執行 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線(xiàn)程2嘗試獲取鎖1..."); synchronized(lock1){ System.out.println("線(xiàn)程2獲得了鎖1"); } } } },"線(xiàn)程2"); thread1.start(); thread2.start(); } }
程序運行結果如下:
可以看出,線(xiàn)程1嘗試獲取了鎖2,線(xiàn)程2嘗試獲取了鎖1,但是二者并沒(méi)有獲取到對方的鎖;這就發(fā)生了所謂的“死鎖”!
想要排查死鎖具體細節,可以通過(guò)三個(gè)工具(位于jdk安裝路徑bin目錄)去排查,現在就給大家介紹一下:
1.jconsole
可以看出,線(xiàn)程1和線(xiàn)程2發(fā)生了死鎖,死鎖發(fā)生的位置一目了然
2.jvisualvm
可以看出,發(fā)生了死鎖,線(xiàn)程1和線(xiàn)程2嘗試獲取的鎖是對方的鎖。
3.jmc
可以看出,同樣檢測出了死鎖情況
無(wú)論是用哪個(gè)工具排查死鎖情況都是OK的。
1.互斥條件(一個(gè)鎖只能被一個(gè)線(xiàn)程占有,當一個(gè)鎖被一個(gè)線(xiàn)程持有之后,不能再被其他線(xiàn)程持有);
2.請求擁有(一個(gè)線(xiàn)程擁有一把鎖之后,又去嘗試請求擁有另外一把鎖);可以解決
3.不可剝奪(一個(gè)鎖被一個(gè)線(xiàn)程占有之后,如果該線(xiàn)程沒(méi)有釋放鎖,其他線(xiàn)程不能強制獲得該鎖);
4.環(huán)路等待條件(多線(xiàn)程獲取鎖時(shí)形成了一個(gè)環(huán)形鏈)可以解決
環(huán)路等待條件相對于請求擁有更容易實(shí)現,那么通過(guò)破壞環(huán)路等待條件
解決死鎖問(wèn)題
破壞環(huán)路等待條件示意圖:
針對于上面死鎖舉例中代碼,解決死鎖,具體看下面代碼:
/** * 線(xiàn)程死鎖問(wèn)題 */ public class DeadLock { public static void main(String[] args) { //創(chuàng )建兩個(gè)鎖對象 Object lock1 = new Object(); Object lock2 = new Object(); //創(chuàng )建子線(xiàn)程 /* 線(xiàn)程1:①先獲得鎖1 ②休眠1s,讓線(xiàn)程2獲得鎖2 ③線(xiàn)程1嘗試獲取鎖2 線(xiàn)程2同理 */ Thread thread1 = new Thread(new Runnable() { @Override public void run() { //線(xiàn)程1業(yè)務(wù)邏輯 synchronized(lock1){ System.out.println("線(xiàn)程1得到了鎖子1"); try { //休眠1s,讓線(xiàn)程2先得到鎖2 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線(xiàn)程1嘗試獲取鎖2..."); synchronized(lock2){ System.out.println("線(xiàn)程1獲得了鎖2!"); } } } },"線(xiàn)程1"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { //線(xiàn)程2業(yè)務(wù)邏輯 synchronized(lock2){ System.out.println("線(xiàn)程2得到了鎖子2"); try { //休眠1s,讓線(xiàn)程1先得到鎖1;因為線(xiàn)程是并發(fā)執行我們不知道誰(shuí)先執行 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線(xiàn)程2嘗試獲取鎖1..."); synchronized(lock1){ System.out.println("線(xiàn)程2獲得了鎖1"); } } } },"線(xiàn)程2"); thread1.start(); thread2.start(); } }
程序運行結果如下:
可以看出,通過(guò)破壞環(huán)路等待條件完美解決了死鎖問(wèn)題
線(xiàn)程通訊機制:一個(gè)線(xiàn)程的動(dòng)作可以讓另外一個(gè)線(xiàn)程感知到,這就是線(xiàn)程通訊機制。
wait():讓當前線(xiàn)程進(jìn)入休眠等待狀態(tài);
notify():?jiǎn)拘旬斍皩ο笊系男菝叩却€(xiàn)程;
notifyAll():?jiǎn)拘旬斍皩ο笊系乃行菝叩却€(xiàn)程。
面試問(wèn)題:
1.wait()使用時(shí)為什么需要加鎖?
因為wait()必須在同步方法或者同步塊中使用,也就是說(shuō)wait()需要配合加鎖一起使用(比如synchronized或Lock),調用對象調用wait()如果沒(méi)有適當的鎖,就會(huì )引發(fā)異常,因此說(shuō)wait()使用時(shí)需要加鎖。
2.wait()使用為什么要釋放鎖?
wait()是Objetc類(lèi)中一個(gè)實(shí)例方法,默認是不傳任何值的,不傳值的時(shí)候表示讓當前線(xiàn)程處于永久休眠等待狀態(tài),這樣會(huì )造成一個(gè)鎖被一個(gè)線(xiàn)程長(cháng)時(shí)間一直擁有,為了避免這種問(wèn)題的發(fā)生,使用wait()后必須釋放鎖。
wait()/notify()/notifyAll()使用時(shí)注意事項:
使用這三個(gè)方法時(shí)都必須進(jìn)行加鎖;
2.加鎖的對象和調用wait()/notify()/notifyAll()對象必須是同一個(gè)對象;
3.一組wait()/notify()/notifyAll()必須是同一個(gè)對象;
4.notify()只能喚醒當前對象上的一個(gè)休眠等到線(xiàn)程;而notifyAll()可以喚醒當前對象上的所有休眠等待線(xiàn)程。
sleep(0)和wait(0)的區別:
1.sleep()是Thread類(lèi)中一個(gè)靜態(tài)方法,wait()是Object類(lèi)中一個(gè)普通的成員方法;
2.sleep(0)會(huì )立即觸發(fā)一次CPU的搶占執行,wait(0)會(huì )讓當前線(xiàn)程無(wú)限休眠等待下去。
wait()和sleep()的區別:
相同點(diǎn):
1.都會(huì )讓當前線(xiàn)程進(jìn)行休眠等待;
2.使用二者時(shí)都需處理InterruptedException異常(try/catch)。
不同點(diǎn):
1.wait()是Object中普通成員方法,sleep是Thread中靜態(tài)方法;
2.wait()使用可以不穿參數,sleep()必須傳入一個(gè)大于等于0的參數;
3.wait()使用時(shí)必須配合加鎖一起使用,sleep()使用時(shí)不需要加鎖;
4.wait()使用時(shí)需要釋放鎖,如果sleep()加鎖后不會(huì )釋放鎖;
5.wait()會(huì )讓當前線(xiàn)程進(jìn)入WAITING狀態(tài)(默認沒(méi)有明確的等待時(shí)間,當被別的線(xiàn)程喚醒或者wait()傳參后超過(guò)等待時(shí)間量自己?jiǎn)拘?,將進(jìn)入就緒狀態(tài)),sleep()會(huì )讓當前線(xiàn)程進(jìn)入TIMED_WAITING狀態(tài)(有明確的結束等待時(shí)間,但是這是死等的方式,休眠結束后進(jìn)入就緒狀態(tài))。
*為什么wait()處于Object中而不是Thread中?(有點(diǎn)繞 我有點(diǎn)懵了…)
wait()的調用必須進(jìn)行加鎖和釋放鎖操作,而鎖是屬于對象級別非線(xiàn)程級別,也就是說(shuō)鎖針對于對象進(jìn)行操作而不是線(xiàn)程;而線(xiàn)程和鎖是一對多的關(guān)系,一個(gè)線(xiàn)程可以擁有多把鎖,而一個(gè)線(xiàn)程只能被一個(gè)線(xiàn)程擁有,為了靈活操作,就將wait()放在Object中。
LockSupport是對wait()的升級,無(wú)需加鎖也無(wú)需釋放鎖;
LockSupport.park()讓線(xiàn)程休眠,和wait()一樣會(huì )讓線(xiàn)程進(jìn)入WAITING狀態(tài);LockSupport.unpark()喚醒線(xiàn)程,可以喚醒對象上指定的休眠等待線(xiàn)程;(優(yōu)勢)
wait()與LockSupport的區別:
相同點(diǎn):
1.二者都可以讓線(xiàn)程進(jìn)入休眠等待狀態(tài);
2.二者都可以傳參或者不傳參,讓線(xiàn)程都會(huì )進(jìn)入到WAITING狀態(tài)。
不同點(diǎn):
1.wait()需要配合加鎖一起使用,LockSupport無(wú)需加鎖;
2.wait()只能喚醒對象的隨機休眠線(xiàn)程和全部線(xiàn)程,LockSupport可以喚醒對象的指定休眠線(xiàn)程。
到此這篇關(guān)于如何解決Java多線(xiàn)程死鎖問(wèn)題的文章就介紹到這了,更多相關(guān)Java多線(xiàn)程死鎖內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站