程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java的線程死鎖

java的線程死鎖

編輯:關於JAVA

由於線程可能進入堵塞狀態,而且由於對象可能擁有“同步”方法——除非同步鎖定被解除,否則線程不能訪問那個對象——所以一個線程完全可能等候另一個對象,而另一個對象又在等候下一個對象,以此類推。這個“等候”鏈最可怕的情形就是進入封閉狀態——最後那個對象等候的是第一個對象!此時,所有線程都會陷入無休止的相互等待狀態,大家都動彈不得。我們將這種情況稱為“死鎖”。盡管這種情況並非經常出現,但一旦碰到,程序的調試將變得異常艱難。
就語言本身來說,尚未直接提供防止死鎖的幫助措施,需要我們通過謹慎的設計來避免。如果有誰需要調試一個死鎖的程序,他是沒有任何竅門可用的。

1. Java 1.2對stop(),suspend(),resume()以及destroy()的反對
為減少出現死鎖的可能,Java 1.2作出的一項貢獻是“反對”使用Thread的stop(),suspend(),resume()以及destroy()方法。
之所以反對使用stop(),是因為它不安全。它會解除由線程獲取的所有鎖定,而且如果對象處於一種不連貫狀態(“被破壞”),那麼其他線程能在那種狀態下檢查和修改它們。結果便造成了一種微妙的局面,我們很難檢查出真正的問題所在。所以應盡量避免使用stop(),應該采用Blocking.java那樣的方法,用一個標志告訴線程什麼時候通過退出自己的run()方法來中止自己的執行。
如果一個線程被堵塞,比如在它等候輸入的時候,那麼一般都不能象在Blocking.java中那樣輪詢一個標志。但在這些情況下,我們仍然不該使用stop(),而應換用由Thread提供的interrupt()方法,以便中止並退出堵塞的代碼。
 

//: Interrupt.java
// The alternative approach to using stop()
// when a thread is blocked
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

class Blocked extends Thread {
  public synchronized void run() {
    try {
      wait(); // Blocks
    } catch(InterruptedException e) {
      System.out.println("InterruptedException");
    }
    System.out.println("Exiting run()");
  }
}

public class Interrupt extends Applet {
  private Button 
    interrupt = new Button("Interrupt");
  private Blocked blocked = new Blocked();
  public void init() {
    add(interrupt);
    interrupt.addActionListener(
      new ActionListener() {
        public 
        void actionPerformed(ActionEvent e) {
          System.out.println("Button pressed");
          if(blocked == null) return;
          Thread remove = blocked;
          blocked = null; // to release it
          remove.interrupt();
        }
      });
    blocked.start();
  }
  public static void main(String[] args) {
    Interrupt applet = new Interrupt();
    Frame aFrame = new Frame("Interrupt");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(200,100);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~


Blocked.run()內部的wait()會產生堵塞的線程。當我們按下按鈕以後,blocked(堵塞)的句柄就會設為null,使垃圾收集器能夠將其清除,然後調用對象的interrupt()方法。如果是首次按下按鈕,我們會看到線程正常退出。但在沒有可供“殺死”的線程以後,看到的便只是按鈕被按下而已。
suspend()和resume()方法天生容易發生死鎖。調用suspend()的時候,目標線程會停下來,但卻仍然持有在這之前獲得的鎖定。此時,其他任何線程都不能訪問鎖定的資源,除非被“掛起”的線程恢復運行。對任何線程來說,如果它們想恢復目標線程,同時又試圖使用任何一個鎖定的資源,就會造成令人難堪的死鎖。所以我們不應該使用suspend()和resume(),而應在自己的Thread類中置入一個標志,指出線程應該活動還是掛起。若標志指出線程應該掛起,便用wait()命其進入等待狀態。若標志指出線程應當恢復,則用一個notify()重新啟動線程。我們可以修改前面的Counter2.java來實際體驗一番。盡管兩個版本的效果是差不多的,但大家會注意到代碼的組織結構發生了很大的變化——為所有“聽眾”都使用了匿名的內部類,而且Thread是一個內部類。這使得程序的編寫稍微方便一些,因為它取消了Counter2.java中一些額外的記錄工作。
 

//: Suspend.java
// The alternative approach to using suspend()
// and resume(), which have been deprecated
// in Java 1.2.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Suspend extends Applet {
  private TextField t = new TextField(10);
  private Button 
    suspend = new Button("Suspend"),
    resume = new Button("Resume");
  class Suspendable extends Thread {
    private int count = 0;
    private boolean suspended = false;
    public Suspendable() { start(); }
    public void fauxSuspend() { 
      suspended = true;
    }
    public synchronized void fauxResume() {
      suspended = false;
      notify();
    }
    public void run() {
      while (true) {
        try {
          sleep(100);
          synchronized(this) {
            while(suspended)
              wait();
          }
        } catch (InterruptedException e){}
        t.setText(Integer.toString(count++));
      }
    }
  } 
  private Suspendable ss = new Suspendable();
  public void init() {
    add(t);
    suspend.addActionListener(
      new ActionListener() {
        public 
        void actionPerformed(ActionEvent e) {
          ss.fauxSuspend();
        }
      });
    add(suspend);
    resume.addActionListener(
      new ActionListener() {
        public 
        void actionPerformed(ActionEvent e) {
          ss.fauxResume();
        }
      });
    add(resume);
  }
  public static void main(String[] args) {
    Suspend applet = new Suspend();
    Frame aFrame = new Frame("Suspend");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,100);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~


Suspendable中的suspended(已掛起)標志用於開關“掛起”或者“暫停”狀態。為掛起一個線程,只需調用fauxSuspend()將標志設為true(真)即可。對標志狀態的偵測是在run()內進行的。就象本章早些時候提到的那樣,wait()必須設為“同步”(synchronized),使其能夠使用對象鎖。在fauxResume()中,suspended標志被設為false(假),並調用notify()——由於這會在一個“同步”從句中喚醒wait(),所以fauxResume()方法也必須同步,使其能在調用notify()之前取得對象鎖(這樣一來,對象鎖可由要喚醍的那個wait()使用)。如果遵照本程序展示的樣式,可以避免使用wait()和notify()。
Thread的destroy()方法根本沒有實現;它類似一個根本不能恢復的suspend(),所以會發生與suspend()一樣的死鎖問題。然而,這一方法沒有得到明確的“反對”,也許會在Java以後的版本(1.2版以後)實現,用於一些可以承受死鎖危險的特殊場合。
大家可能會奇怪當初為什麼要實現這些現在又被“反對”的方法。之所以會出現這種情況,大概是由於Sun公司主要讓技術人員來決定對語言的改動,而不是那些市場銷售人員。通常,技術人員比搞銷售的更能理解語言的實質。當初犯下了錯誤以後,也能較為理智地正視它們。這意味著Java能夠繼續進步,即便這使Java程序員多少感到有些不便。就我自己來說,寧願面對這些不便之處,也不願看到語言停滯不前。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved