java的線程死結

來源:互聯網
上載者:User

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

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 blockedimport 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程式員多少感到有些不便。就我自己來說,寧願面對這些不便之處,也不願看到語言停滯不前。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.