我參與的幾個項目中,死鎖貌似未做太多考慮,可能項目對併發的安全和性能要求不高吧。但是在面試的時候,死鎖的問題依然常常被提起。這裏就簡單介紹下造成死鎖的原因及避免方法。
假如有2個線程,一個線程想先鎖對象1,再鎖對象2,恰好另外有一個線程先鎖對象2,再鎖對象1。
在這個過程中,當線程1把對象1鎖好以後,就想去鎖對象2,但是不巧,線程2已經把對象2鎖上了,也正在嘗試去鎖對象1。
什麼時候結束呢,只有線程1把2個對象都鎖上並把方法執行完,並且線程2把2個對象也都鎖上並且把方法執行完畢,那麼就結束了,但是,誰都不肯放掉已經鎖上的對象,所以就沒有結果,這種情況就叫做線程死鎖。下面有個實例程序模擬死鎖:
package org.thread;
public class Synchron {
public void begin() {
Thread t1 = new Thread(new Thread1());
Thread t2 = new Thread(new Thread2());
t1.start();
t2.start();
}
public static void main(String[] args) {
new Synchron().begin();
}
public synchronized void getI() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
getJ();
}
public synchronized void getJ() {
getI();
}
class Thread1 implements Runnable {
public void run() {
getI();
}
}
class Thread2 implements Runnable {
public void run() {
getJ();
}
}
}
那麼我們需要注意的是,線程死鎖的原因不是因爲相互調用,而是由線程對資源的佔用和等待導致的!
有一種避免死鎖的辦法是,儘可能鎖大的對象,即加大鎖的粒度;也應避免同時鎖多個對象。
再簡單說說wait和notify:
1).wait
Object類中的final方法,有InterruptedException。它的作用是導致當前的線程等待,直到其它線程調用此對象的notify方法或者notifyAll方法,wait還有一些重用方法,傳參數,比如說時間長度。
當前的線程必須擁有此對象監視器,然後該線程發佈對此監視器的所有權並且開始等待,直到其它線程通過調用notify方法或者notifyAll方法,通知在此對象的監視器上等待的線程醒來,然後該線程將等到重新獲得對監視器的所有權後才能開始執行。
說說wait和sleep的區別
首先sleep
sleep是Thread裏面的方法,在被執行的時候,鎖並不會被交出去,要直到sleep所在的方法全部被執行完畢以後才交出鎖。
wait是Object裏面的方法,在被執行的時候,鎖被解除,由其它線程去爭奪,直到有notify或者notifyAll方法喚醒它。
2).Notify
也是Object類中的方法,用於喚醒在此對象上等待着的某一個線程,如果有很多線程掛起的話,就隨機地決定哪一個。注意,是隨機的,這時可以用notifyAll來喚醒所有的。一定要注意這個問題,除非你明確地知道你在做什麼,否則最好就是用notifyAll。
注意事項:
wait()和notify()必須包括在synchronized代碼塊中,等待中的線程必須由notify()方法顯式地喚醒,否則它會永遠地等待下去。很多人初級接觸多線程時,會習慣把wait()和notify()放在run()方法裏,一定要謹記,這兩個方法屬於某個對象,應在對象所在的類方法中定義它,然後run中去調用它。