只有光頭才能變強html
回顧前面:編程
本篇主要是講解死鎖,這是我在多線程的最後一篇了。主要將多線程的基礎過一遍,之後有機會再繼續深刻!微信
死鎖是在多線程中也是比較重要的知識點了!多線程
那麼接下來就開始吧,若是文章有錯誤的地方請你們多多包涵,不吝在評論區指正哦~併發
聲明:本文使用JDK1.8ide
在Java中使用多線程,就會有可能致使死鎖問題。死鎖會讓程序一直卡住,再也不程序往下執行。咱們只能經過停止並重啓的方式來讓程序從新執行。工具
形成死鎖的緣由能夠歸納成三句話:oop
首先咱們來看一下最簡單的死鎖(鎖順序死鎖)是怎麼樣發生的:post
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(); } } } }
咱們的線程是交錯執行的,那麼就頗有可能出現如下的狀況:學習
leftRight()
方法,獲得left鎖rightLeft()
方法,獲得right鎖
咱們看一下下面的例子,你認爲會發生死鎖嗎?
// 轉帳 public static void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount) throws InsufficientFundsException { // 鎖定匯帳帳戶 synchronized (fromAccount) { // 鎖定來帳帳戶 synchronized (toAccount) { // 判餘額是否大於0 if (fromAccount.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsException(); } else { // 匯帳帳戶減錢 fromAccount.debit(amount); // 來帳帳戶增錢 toAccount.credit(amount); } } } }
上面的代碼看起來是沒有問題的:鎖定兩個帳戶來判斷餘額是否充足才進行轉帳!
可是,一樣有可能會發生死鎖:
transferMoney()
A:transferMoney(myAccount,yourAccount,10); B:transferMoney(yourAccount,myAccount,20);
咱們來看一下下面的例子:
public class CooperatingDeadlock { // Warning: deadlock-prone! class Taxi { @GuardedBy("this") private Point location, destination; private final Dispatcher dispatcher; public Taxi(Dispatcher dispatcher) { this.dispatcher = dispatcher; } public synchronized Point getLocation() { return location; } // setLocation 須要Taxi內置鎖 public synchronized void setLocation(Point location) { this.location = location; if (location.equals(destination)) // 調用notifyAvailable()須要Dispatcher內置鎖 dispatcher.notifyAvailable(this); } public synchronized Point getDestination() { return destination; } public synchronized void setDestination(Point destination) { this.destination = destination; } } class Dispatcher { @GuardedBy("this") private final Set<Taxi> taxis; @GuardedBy("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); } // 調用getImage()須要Dispatcher內置鎖 public synchronized Image getImage() { Image image = new Image(); for (Taxi t : taxis) // 調用getLocation()須要Taxi內置鎖 image.drawMarker(t.getLocation()); return image; } } class Image { public void drawMarker(Point p) { } } }
上面的getImage()
和setLocation(Point location)
都須要獲取兩個鎖的
這就是隱式獲取兩個鎖(對象之間協做)..
這種方式也很容易就形成死鎖.....
避免死鎖能夠歸納成三種方法:
tryLock()
上面transferMoney()
發生死鎖的緣由是由於加鎖順序不一致而出現的~
那麼上面的例子咱們就能夠改造成這樣子:
public class InduceLockOrder { // 額外的鎖、避免兩個對象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); // 根據hash值來上鎖 if (fromHash < toHash) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } else if (fromHash > toHash) {// 根據hash值來上鎖 synchronized (toAcct) { synchronized (fromAcct) { new Helper().transfer(); } } } else {// 額外的鎖、避免兩個對象hash值相等的狀況(即便不多) synchronized (tieLock) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } } } }
獲得對應的hash值來固定加鎖的順序,這樣咱們就不會發生死鎖的問題了!
在協做對象之間發生死鎖的例子中,主要是由於在調用某個方法時就須要持有鎖,而且在方法內部也調用了其餘帶鎖的方法!
咱們能夠這樣來改造:
class CooperatingNoDeadlock { @ThreadSafe class Taxi { @GuardedBy("this") 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) { boolean reachedDestination; // 加Taxi內置鎖 synchronized (this) { this.location = location; reachedDestination = location.equals(destination); } // 執行同步代碼塊後完畢,釋放鎖 if (reachedDestination) // 加Dispatcher內置鎖 dispatcher.notifyAvailable(this); } public synchronized Point getDestination() { return destination; } public synchronized void setDestination(Point destination) { this.destination = destination; } } @ThreadSafe class Dispatcher { @GuardedBy("this") private final Set<Taxi> taxis; @GuardedBy("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); } public Image getImage() { Set<Taxi> copy; // Dispatcher內置鎖 synchronized (this) { copy = new HashSet<Taxi>(taxis); } // 執行同步代碼塊後完畢,釋放鎖 Image image = new Image(); for (Taxi t : copy) // 加Taix內置鎖 image.drawMarker(t.getLocation()); return image; } } class Image { public void drawMarker(Point p) { } } }
使用開放調用是很是好的一種方式,應該儘可能使用它~
使用顯式Lock鎖,在獲取鎖時使用tryLock()
方法。當等待超過期限的時候,tryLock()
不會一直等待,而是返回錯誤信息。
使用tryLock()
可以有效避免死鎖問題~~
雖然形成死鎖的緣由是由於咱們設計得不夠好,可是可能寫代碼的時候不知道哪裏發生了死鎖。
JDK提供了兩種方式來給咱們檢測:
具體可參考:
發生死鎖的緣由主要因爲:
tryLock()
定時鎖,超過期限則返回錯誤信息在操做系統層面上看待死鎖問題(這是我以前作的筆記、很淺顯):
參考資料:
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:Java3y。
文章的目錄導航: