多线程之死锁就是这么简单
前言
只有光頭才能變強(qiáng)
回顧前面:
- ThreadLocal就是這么簡單
- 多線程三分鐘就可以入個(gè)門了!
- 多線程基礎(chǔ)必要知識(shí)點(diǎn)!看了學(xué)習(xí)多線程事半功倍
- Java鎖機(jī)制了解一下
- AQS簡簡單單過一遍
- Lock鎖子類了解一下
- 線程池你真不來了解一下嗎?
本篇主要是講解死鎖,這是我在多線程的最后一篇了。主要將多線程的基礎(chǔ)過一遍,以后有機(jī)會(huì)再繼續(xù)深入!
死鎖是在多線程中也是比較重要的知識(shí)點(diǎn)了!
那么接下來就開始吧,如果文章有錯(cuò)誤的地方請(qǐng)大家多多包涵,不吝在評(píng)論區(qū)指正哦~
聲明:本文使用JDK1.8
一、死鎖講解
在Java中使用多線程,就會(huì)有可能導(dǎo)致死鎖問題。死鎖會(huì)讓程序一直卡住,不再程序往下執(zhí)行。我們只能通過中止并重啟的方式來讓程序重新執(zhí)行。
- 這是我們非常不愿意看到的一種現(xiàn)象,我們要盡可能避免死鎖的情況發(fā)生!
造成死鎖的原因可以概括成三句話:
- 當(dāng)前線程擁有其他線程需要的資源
- 當(dāng)前線程等待其他線程已擁有的資源
- 都不放棄自己擁有的資源
1.1鎖順序死鎖
首先我們來看一下最簡單的死鎖(鎖順序死鎖)是怎么樣發(fā)生的:
public class LeftRightDeadlock {private final Object left = new Object();private final Object right = new Object();public void leftRight() {// 得到left鎖synchronized (left) {// 得到right鎖synchronized (right) {doSomething();}}}public void rightLeft() {// 得到right鎖synchronized (right) {// 得到left鎖synchronized (left) {doSomethingElse();}}} } 復(fù)制代碼我們的線程是交錯(cuò)執(zhí)行的,那么就很有可能出現(xiàn)以下的情況:
- 線程A調(diào)用leftRight()方法,得到left鎖
- 同時(shí)線程B調(diào)用rightLeft()方法,得到right鎖
- 線程A和線程B都繼續(xù)執(zhí)行,此時(shí)線程A需要right鎖才能繼續(xù)往下執(zhí)行。此時(shí)線程B需要left鎖才能繼續(xù)往下執(zhí)行。
- 但是:線程A的left鎖并沒有釋放,線程B的right鎖也沒有釋放。
- 所以他們都只能等待,而這種等待是無期限的-->永久等待-->死鎖
1.2動(dòng)態(tài)鎖順序死鎖
我們看一下下面的例子,你認(rèn)為會(huì)發(fā)生死鎖嗎?
// 轉(zhuǎn)賬public static void transferMoney(Account fromAccount,Account toAccount,DollarAmount amount)throws InsufficientFundsException {// 鎖定匯賬賬戶synchronized (fromAccount) {// 鎖定來賬賬戶synchronized (toAccount) {// 判余額是否大于0if (fromAccount.getBalance().compareTo(amount) < 0) {throw new InsufficientFundsException();} else {// 匯賬賬戶減錢fromAccount.debit(amount);// 來賬賬戶增錢toAccount.credit(amount);}}}} 復(fù)制代碼上面的代碼看起來是沒有問題的:鎖定兩個(gè)賬戶來判斷余額是否充足才進(jìn)行轉(zhuǎn)賬!
但是,同樣有可能會(huì)發(fā)生死鎖:
- 如果兩個(gè)線程同時(shí)調(diào)用transferMoney()
- 線程A從X賬戶向Y賬戶轉(zhuǎn)賬
- 線程B從賬戶Y向賬戶X轉(zhuǎn)賬
- 那么就會(huì)發(fā)生死鎖。
1.3協(xié)作對(duì)象之間發(fā)生死鎖
我們來看一下下面的例子:
public class CooperatingDeadlock {// Warning: deadlock-prone!class Taxi {("this") private Point location, destination;private final Dispatcher dispatcher;public Taxi(Dispatcher dispatcher) {this.dispatcher = dispatcher;}public synchronized Point getLocation() {return location;}// setLocation 需要Taxi內(nèi)置鎖public synchronized void setLocation(Point location) {this.location = location;if (location.equals(destination))// 調(diào)用notifyAvailable()需要Dispatcher內(nèi)置鎖dispatcher.notifyAvailable(this);}public synchronized Point getDestination() {return destination;}public synchronized void setDestination(Point destination) {this.destination = destination;}}class Dispatcher {("this") private final Set<Taxi> taxis;("this") private final Set<Taxi> availableTaxis;public Dispatcher() {taxis = new HashSet<Taxi>();availableTaxis = new HashSet<Taxi>();}public synchronized void notifyAvailable(Taxi taxi) {availableTaxis.add(taxi);}// 調(diào)用getImage()需要Dispatcher內(nèi)置鎖public synchronized Image getImage() {Image image = new Image();for (Taxi t : taxis)// 調(diào)用getLocation()需要Taxi內(nèi)置鎖image.drawMarker(t.getLocation());return image;}}class Image {public void drawMarker(Point p) {}} } 復(fù)制代碼上面的getImage()和setLocation(Point location)都需要獲取兩個(gè)鎖的
- 并且在操作途中是沒有釋放鎖的
這就是隱式獲取兩個(gè)鎖(對(duì)象之間協(xié)作)..
這種方式也很容易就造成死鎖.....
二、避免死鎖的方法
避免死鎖可以概括成三種方法:
- 固定加鎖的順序(針對(duì)鎖順序死鎖)
- 開放調(diào)用(針對(duì)對(duì)象之間協(xié)作造成的死鎖)
- 使用定時(shí)鎖-->tryLock()
- 如果等待獲取鎖時(shí)間超時(shí),則拋出異常而不是一直等待!
2.1固定鎖順序避免死鎖
上面transferMoney()發(fā)生死鎖的原因是因?yàn)?strong>加鎖順序不一致而出現(xiàn)的~
- 正如書上所說的:如果所有線程以固定的順序來獲得鎖,那么程序中就不會(huì)出現(xiàn)鎖順序死鎖問題!
那么上面的例子我們就可以改造成這樣子:
public class InduceLockOrder {// 額外的鎖、避免兩個(gè)對(duì)象hash值相等的情況(即使很少)private static final Object tieLock = new Object();public void transferMoney(final Account fromAcct,final Account toAcct,final DollarAmount amount)throws InsufficientFundsException {class Helper {public void transfer() throws InsufficientFundsException {if (fromAcct.getBalance().compareTo(amount) < 0)throw new InsufficientFundsException();else {fromAcct.debit(amount);toAcct.credit(amount);}}}// 得到鎖的hash值int fromHash = System.identityHashCode(fromAcct);int toHash = System.identityHashCode(toAcct);// 根據(jù)hash值來上鎖if (fromHash < toHash) {synchronized (fromAcct) {synchronized (toAcct) {new Helper().transfer();}}} else if (fromHash > toHash) {// 根據(jù)hash值來上鎖synchronized (toAcct) {synchronized (fromAcct) {new Helper().transfer();}}} else {// 額外的鎖、避免兩個(gè)對(duì)象hash值相等的情況(即使很少)synchronized (tieLock) {synchronized (fromAcct) {synchronized (toAcct) {new Helper().transfer();}}}}} } 復(fù)制代碼得到對(duì)應(yīng)的hash值來固定加鎖的順序,這樣我們就不會(huì)發(fā)生死鎖的問題了!
2.2開放調(diào)用避免死鎖
在協(xié)作對(duì)象之間發(fā)生死鎖的例子中,主要是因?yàn)樵?strong>調(diào)用某個(gè)方法時(shí)就需要持有鎖,并且在方法內(nèi)部也調(diào)用了其他帶鎖的方法!
- 如果在調(diào)用某個(gè)方法時(shí)不需要持有鎖,那么這種調(diào)用被稱為開放調(diào)用!
我們可以這樣來改造:
- 同步代碼塊最好僅被用于保護(hù)那些涉及共享狀態(tài)的操作!
使用開放調(diào)用是非常好的一種方式,應(yīng)該盡量使用它~
2.3使用定時(shí)鎖
使用顯式Lock鎖,在獲取鎖時(shí)使用tryLock()方法。當(dāng)?shù)却?strong>超過時(shí)限的時(shí)候,tryLock()不會(huì)一直等待,而是返回錯(cuò)誤信息。
使用tryLock()能夠有效避免死鎖問題~~
2.4死鎖檢測
雖然造成死鎖的原因是因?yàn)槲覀冊O(shè)計(jì)得不夠好,但是可能寫代碼的時(shí)候不知道哪里發(fā)生了死鎖。
JDK提供了兩種方式來給我們檢測:
- JconsoleJDK自帶的圖形化界面工具,使用JDK給我們的的工具JConsole
- Jstack是JDK自帶的命令行工具,主要用于線程Dump分析。
具體可參考:
- www.cnblogs.com/flyingeagle…
三、總結(jié)
發(fā)生死鎖的原因主要由于:
- 線程之間交錯(cuò)執(zhí)行
- 解決:以固定的順序加鎖
- 執(zhí)行某方法時(shí)就需要持有鎖,且不釋放
- 解決:縮減同步代碼塊范圍,最好僅操作共享變量時(shí)才加鎖
- 永久等待
- 解決:使用tryLock()定時(shí)鎖,超過時(shí)限則返回錯(cuò)誤信息
在操作系統(tǒng)層面上看待死鎖問題(這是我之前做的筆記、很淺顯):
- 操作系統(tǒng)第五篇【死鎖】
參考資料:
- 《Java核心技術(shù)卷一》
- 《Java并發(fā)編程實(shí)戰(zhàn)》
- 《計(jì)算機(jī)操作系統(tǒng) 湯小丹》
如果文章有錯(cuò)的地方歡迎指正,大家互相交流。習(xí)慣在微信看技術(shù)文章,想要獲取更多的Java資源的同學(xué),可以關(guān)注微信公眾號(hào):Java3y。
文章的目錄導(dǎo)航:
- zhongfucheng.bitcron.com/post/shou-j…
總結(jié)
以上是生活随笔為你收集整理的多线程之死锁就是这么简单的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 租金回报率 租金的回报率是怎么算的
- 下一篇: win10壁纸路径