您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)java中出現(xiàn)死鎖如何解決,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
1.一個(gè)最簡(jiǎn)單的死鎖案例
當(dāng)一個(gè)線(xiàn)程永遠(yuǎn)地持有一個(gè)鎖,并且其他線(xiàn)程都嘗試獲得這個(gè)鎖時(shí),那么它們將永遠(yuǎn)被阻塞。在線(xiàn)程A持有鎖L并想獲得鎖M的同時(shí),線(xiàn)程B持有鎖M并嘗試獲得鎖L,那么這兩個(gè)線(xiàn)程將永遠(yuǎn)地等待下去。這種就是最簡(jiǎn)答的死鎖形式(或者叫做"抱死")。
2.鎖順序死鎖
如圖:leftRight和rightLeft這兩個(gè)方法分別獲得left鎖和right鎖。如果一個(gè)線(xiàn)程調(diào)用了leftRight,而另一個(gè)線(xiàn)程調(diào)用了rightLeft,并且這兩個(gè)線(xiàn)程的操作是交互執(zhí)行,那么它們就會(huì)發(fā)生死鎖。
死鎖的原因就是兩個(gè)線(xiàn)程試圖以不同的順序來(lái)獲得相同的鎖。所以,如果所有的線(xiàn)程以固定的順序來(lái)獲得鎖,那么在程序中就不會(huì)出現(xiàn)鎖順序死鎖的問(wèn)題。
2.1.動(dòng)態(tài)的鎖順序死鎖
我以一個(gè)經(jīng)典的轉(zhuǎn)賬案例來(lái)進(jìn)行說(shuō)明,我們知道轉(zhuǎn)賬就是將資金從一個(gè)賬戶(hù)轉(zhuǎn)入另一個(gè)賬戶(hù)。在開(kāi)始轉(zhuǎn)賬之前,首先需要獲得這兩個(gè)賬戶(hù)對(duì)象得鎖,以確保通過(guò)原子方式來(lái)更新兩個(gè)賬戶(hù)中的余額,同時(shí)又不破壞一些不變形條件,例如 賬戶(hù)的余額不能為負(fù)數(shù)。
所以寫(xiě)出的代碼如下:
//動(dòng)態(tài)的鎖的順序死鎖 public class DynamicOrderDeadlock { public static void transferMoney(Account fromAccount,Account toAccount,int amount,int from_index,int to_index) throws Exception { System.out.println("賬戶(hù) "+ from_index+"~和賬戶(hù)~(yú)"+to_index+" ~請(qǐng)求鎖"); synchronized (fromAccount) { System.out.println(" 賬戶(hù) >>>"+from_index+" <<<獲得鎖"); synchronized (toAccount) { System.out.println(" 賬戶(hù) "+from_index+" & "+to_index+"都獲得鎖"); if (fromAccount.compareTo(amount) < 0) { throw new Exception(); }else { fromAccount.debit(amount); toAccount.credit(amount); } } } } static class Account { private int balance = 100000;//這里假設(shè)每個(gè)人賬戶(hù)里面初始化的錢(qián) private final int accNo; private static final AtomicInteger sequence = new AtomicInteger(); public Account() { accNo = sequence.incrementAndGet(); } void debit(int m) throws InterruptedException { Thread.sleep(5);//模擬操作時(shí)間 balance = balance + m; } void credit(int m) throws InterruptedException { Thread.sleep(5);//模擬操作時(shí)間 balance = balance - m; } int getBalance() { return balance; } int getAccNo() { return accNo; } public int compareTo(int money) { if (balance > money) { return 1; }else if (balance < money) { return -1; }else { return 0; } } } }
public class DemonstrateDeadLock { private static final int NUM_THREADS = 5; private static final int NUM_ACCOUNTS = 5; private static final int NUM_ITERATIONS = 100000; public static void main(String[] args) { final Random rnd = new Random(); final Account[] accounts = new Account[NUM_ACCOUNTS]; for(int i = 0;i < accounts.length;i++) { accounts[i] = new Account(); } class TransferThread extends Thread{ @Override public void run() { for(int i = 0;i < NUM_ITERATIONS;i++) { int fromAcct = rnd.nextInt(NUM_ACCOUNTS); int toAcct =rnd.nextInt(NUM_ACCOUNTS); int amount = rnd.nextInt(100); try { DynamicOrderDeadlock.transferMoney(accounts[fromAcct],accounts[toAcct], amount,fromAcct,toAcct); //InduceLockOrder.transferMoney(accounts[fromAcct],accounts[toAcct], amount); //InduceLockOrder2.transferMoney(accounts[fromAcct],accounts[toAcct], amount); }catch (Exception e) { System.out.println("發(fā)生異常-------"+e); } } } } for(int i = 0;i < NUM_THREADS;i++) { new TransferThread().start(); } } }
打印結(jié)果如下:
注意:這里的結(jié)果是我把已經(jīng)執(zhí)行完的給刪除后,只剩下導(dǎo)致死鎖的請(qǐng)求.
從打印結(jié)果的圖片中可以的得到結(jié)論:由于我們無(wú)法控制transferMoney中的參數(shù)的順序,而這些參數(shù)順序取決于外部的輸入。所以?xún)蓚€(gè)線(xiàn)程同時(shí)調(diào)用transferMoney,一個(gè)線(xiàn)程從X向Y轉(zhuǎn)賬,另一個(gè)線(xiàn)程從Y向X轉(zhuǎn)賬,那么就會(huì)發(fā)生互相等待鎖的情況,導(dǎo)致死鎖。
解決問(wèn)題方案:定義鎖的順序,并且整個(gè)應(yīng)用中都按照這個(gè)順序來(lái)獲取鎖。
方案一
使用System.identityHashCode方法,該方法返回有Object.hashCode返回的值,此時(shí)可以通過(guò)某種任意方法來(lái)決定鎖的順序。但是在極少數(shù)情況下,兩個(gè)對(duì)象可能擁有相同的散列值,在這種情況下,通過(guò)給公共變量加鎖來(lái)實(shí)現(xiàn)給鎖制定順序。所以這種方法也是用最小的代價(jià),換來(lái)了最大的安全性。
具體代碼如下:
//通過(guò)鎖順序來(lái)避免死鎖 public class InduceLockOrder { private static final Object tieLock = new Object(); public static void transferMoney(final Account fromAcct, final Account toAcct, final int amount) throws Exception { class Helper { public void transfer() throws Exception { if (fromAcct.compareTo(amount) < 0) { throw new Exception(); } else { fromAcct.debit(amount); toAcct.credit(amount); } } } int fromHash = System.identityHashCode(fromAcct); int toHash = System.identityHashCode(toAcct); if (fromHash < toHash) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } else if (fromHash > toHash) { synchronized (toAcct) { synchronized (fromAcct) { new Helper().transfer(); } } } else { synchronized (tieLock) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } } } static class Account { private int balance = 100000; public Account() { } void debit(int m) throws InterruptedException { Thread.sleep(5); balance = balance + m; } void credit(int m) throws InterruptedException { Thread.sleep(5); balance = balance - m; } int getBalance() { return balance; } public int compareTo(int money) { if (balance > money) { return 1; }else if (balance < money) { return -1; }else { return 0; } } } }
經(jīng)過(guò)我測(cè)試,此方案可行,不會(huì)造成死鎖。
方案二
在A(yíng)ccount中包含一個(gè)唯一的,不可變的,值。比如說(shuō)賬號(hào)等。通過(guò)對(duì)這個(gè)值對(duì)對(duì)象進(jìn)行排序。
具體代碼如下
public class InduceLockOrder2 { public static void transferMoney(final Account fromAcct, final Account toAcct, final int amount) throws Exception { class Helper { public void transfer() throws Exception { if (fromAcct.compareTo(amount) < 0) { throw new Exception(); } else { fromAcct.debit(amount); toAcct.credit(amount); } } } int fromHash = fromAcct.getAccNo(); int toHash = toAcct.getAccNo(); if (fromHash < toHash) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } else if (fromHash > toHash) { synchronized (toAcct) { synchronized (fromAcct) { new Helper().transfer(); } } } } static class Account { private int balance = 100000; private final int accNo; private static final AtomicInteger sequence = new AtomicInteger(); public Account() { accNo = sequence.incrementAndGet(); } void debit(int m) throws InterruptedException { Thread.sleep(6); balance = balance + m; } void credit(int m) throws InterruptedException { Thread.sleep(6); balance = balance - m; } int getBalance() { return balance; } int getAccNo() { return accNo; } public int compareTo(int money) { if (balance > money) { return 1; }else if (balance < money) { return -1; }else { return 0; } } } }
經(jīng)過(guò)測(cè)試此方案也可行。
2.2在協(xié)作對(duì)象之間發(fā)生的死鎖
如果在持有鎖時(shí)調(diào)用某外部的方法,那么將出現(xiàn)活躍性問(wèn)題。在這個(gè)外部方法中可能會(huì)獲取其他的鎖(這個(gè)可能產(chǎn)生死鎖),或阻塞時(shí)間過(guò)長(zhǎng),導(dǎo)致其他線(xiàn)程無(wú)法及時(shí)獲得當(dāng)前持有的鎖。
場(chǎng)景如下:Taxi代表出租車(chē)對(duì)象,包含當(dāng)前位置和目的地。Dispatcher代表車(chē)隊(duì)。當(dāng)一個(gè)線(xiàn)程收到GPS更新事件時(shí)掉用setLocation,那么它首先更新出租車(chē)的位置,然后判斷它是否到達(dá)目的地。如果已經(jīng)到達(dá),它會(huì)通知Dispatcher:它需要一個(gè)新的目的地。因?yàn)閟etLocation和notifyAvailable都是同步方法,因此掉用setLocation線(xiàn)程首先獲取taxi的鎖,然后在獲取Dispatcher的鎖。同樣,掉用getImage的線(xiàn)程首先獲取Dispatcher的鎖,再獲取每一個(gè)taxi的鎖,這兩個(gè)線(xiàn)程按照不同的順序來(lái)獲取鎖,因此可能導(dǎo)致死鎖。
能造成死鎖的代碼如下:
//會(huì)發(fā)生死鎖 public class CooperatingDeadLock { // 坐標(biāo)類(lèi) class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } } // 出租車(chē)類(lèi) class Taxi { private Point location, destination; private final Dispatcher dispatcher; public Taxi(Dispatcher dispatcher) { this.dispatcher = dispatcher; } public synchronized Point getLocation() { return location; } public synchronized void setLocation(Point location) { this.location = location; if (location.equals(destination)) { dispatcher.notifyAvailable(this); } } public synchronized Point getDestination() { return destination; } public synchronized void setDestination(Point destination) { this.destination = destination; } } class Dispatcher { private final Set<Taxi> taxis; private final Set<Taxi> availableTaxis; public Dispatcher() { taxis = new HashSet<>(); availableTaxis = new HashSet<>(); } public synchronized void notifyAvailable(Taxi taxi) { availableTaxis.add(taxi); } public synchronized Image getImage() { Image image = new Image(); for(Taxi t:taxis) { image.drawMarker(t.getLocation()); } return image; } } class Image{ public void drawMarker(Point p) { } } }
解決方案:使用開(kāi)放掉用。
如果再調(diào)用某個(gè)方法時(shí)不需要持有鎖,那么這種調(diào)用就被稱(chēng)為開(kāi)放掉用。這種調(diào)用能有效的避免死鎖,并且易于分析線(xiàn)程安全。
修改后的代碼如下:
//此方案不會(huì)造成死鎖 public class CooperatingNoDeadlock { // 坐標(biāo)類(lèi) class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } } // 出租車(chē)類(lèi) class Taxi { private Point location, destination; private final Dispatcher dispatcher; public Taxi(Dispatcher dispatcher) { this.dispatcher = dispatcher; } public synchronized Point getLocation() { return location; } public void setLocation(Point location) { boolean reachedDestination; synchronized (this) { this.location = location; reachedDestination = location.equals(destination); } if (reachedDestination) { dispatcher.notifyAvailable(this); } } public synchronized Point getDestination() { return destination; } public synchronized void setDestination(Point destination) { this.destination = destination; } } class Dispatcher { private final Set<Taxi> taxis; private final Set<Taxi> availableTaxis; public Dispatcher() { taxis = new HashSet<>(); availableTaxis = new HashSet<>(); } public synchronized void notifyAvailable(Taxi taxi) { availableTaxis.add(taxi); } public Image getImage() { Set<Taxi> copy; synchronized (this) { copy = new HashSet<>(taxis); } Image image = new Image(); for(Taxi t:copy) { image.drawMarker(t.getLocation()); } return image; } } class Image{ public void drawMarker(Point p) { } } }
看完上述內(nèi)容,你們對(duì)java中出現(xiàn)死鎖如何解決有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。