本文內容
同步不是改善程序安全性的靈丹妙藥。
發生死鎖的兩種情況和解決方法。
同步不是改善程序安全性的靈丹妙藥
從《線程的同步》一節中我們可以知道,synchronized能保證只有一個線程進入同步方法或同步塊,但為了安全性盲目給多線程程序加上synchronized關鍵字并不是問題解決之道,這不但會降低程序的效率;還有可能帶來嚴重的問題-死鎖。
死鎖發生在兩個或以上的線程在等待對象鎖被釋放,但程序的環境卻讓lock無法釋放時。下面我們將看到兩種類型的死鎖例子。
某線程不退出同步函數造成的死鎖
public class PaintBoard extends Thread{
private boolean flag=true;
public void paint(){
System.out.println("模擬繪畫");
}
public synchronized void run(){
while(flag){
try{
paint();
Thread.sleep(1000);
}
catch(InterruptedException ex){
ex.printStackTrace();
}
}
}
public synchronized void stopDraw(){
flag=false;
System.out.println("禁止繪畫");
}
public static void main(String[] args){
PaintBoard paintBoard=new PaintBoard();
paintBoard.start();
new StopThread(paintBoard);
}
}
public class StopThread implements Runnable{
private PaintBoard paintBoard;
public StopThread(PaintBoard paintBoard){
this.paintBoard=paintBoard;
Thread th=new Thread(this);
th.start();
}
public void run(){
while(true){
System.out.println("試圖停止繪畫過程");
paintBoard.stopDraw();
System.out.println("停止繪畫過程完成");
}
}
}
問題的發生和解決
剛才的死鎖原因是run()函數中有一個無限循環,一個線程進入后會在其中往復操作,這使它永遠不會放棄對this的鎖定,結果導致其它線程無法獲得this的鎖定而進入stopDraw函數。
我們把修飾run函數的synchronized取消就能解決問題。 run函數中不會改變任何量,這種函數是不該加上synchronized的。
兩個線程爭搶資源造成的死鎖.
public class Desk{
private Object fork=new Object();
private Object knife=new Object();
public void eatForLeft(){
synchronized(fork){
System.out.println("左撇子拿起叉");
sleep(1);
synchronized(knife){
System.out.println("左撇子拿起刀");
System.out.println("左撇子開始吃飯");
}
}
}
public void eatForRight(){
synchronized(knife){
System.out.println("右撇子拿起刀");
sleep(1);
synchronized(fork){
System.out.println("右撇子拿起叉");
System.out.println("右撇子開始吃飯");
}
}
}
private void sleep(int second){
try{
Thread.sleep(second*1000);
}
catch(InterruptedException ex){
ex.printStackTrace();
}
}
public static void main(String[] args){
Desk desk=new Desk();
new LeftHand(desk);
new RightHand(desk);
}
}
public class LeftHand implements Runnable{
private Desk desk;
public LeftHand(Desk desk){
this.desk=desk;
Thread th=new Thread(this);
th.start();
}
public void run(){
while(true){
desk.eatForLeft();
}
}
}
public class RightHand implements Runnable{
private Desk desk;
public RightHand(Desk desk){
this.desk=desk;
Thread th=new Thread(this);
th.start();
}
public void run(){
while(true){
desk.eatForRight();
}
}
}
問題的發生和解決
這部分程序中于兩個線程都要獲得兩個對象的鎖定才能執行實質性操作,但運行起來卻發現其它線程持有了自己需要的另一個鎖定,于是停在Wait Set中等待對方釋放這個鎖定,結果造成了死鎖。
解決這個問題的方法是保證鎖對象的持有順序,如果兩個加上了同步的函數都是先刀后叉的形式則不會發生問題。
小結
同步不是改善程序安全性的靈丹妙藥,盲目同步也會導致嚴重的問題-死鎖.
某線程持續不退出同步函數會造成死鎖.解決方法是去掉或更換不正確的同步。
兩個線程都等待對方釋放自己需要的資源也會造成死鎖.這種情況的解決方法是確保同步鎖對象的持有順序。