1. 程式人生 > >Java多執行緒中,Join和Interrupt()方法的使用

Java多執行緒中,Join和Interrupt()方法的使用

更多詳細的解答請轉至:http://my.oschina.net/summerpxy/blog/198457;http://uule.iteye.com/blog/1101994;(比如有一個執行緒t.當在Main執行緒中呼叫t.join()的時候,那麼Main執行緒必須拿到t執行緒的鎖。否則,Main執行緒是無法wait()的。join()其實是和wai()聯絡在一起的。

一個執行緒的啟動比較的簡單,就是start()方法就可以了。但是在結束這個執行緒的時候,不能夠使用stop()方法。一般情況下,我們會使用Interrupt()方法。

在此之前我想說明的一點就是,當我們呼叫interrupt()方法的時候,我們只是給這個執行緒設定了一箇中斷的標準,而至於中斷還是不中斷,需要執行緒本身進行處理。這是要注意的。當然我們也可以通過檢查是否執行緒是否處於中斷狀態來避免這個問題。比如說,如果這個執行緒本身是處於中斷狀態的,那麼久設定這個執行緒不能休眠,這是要注意的。中斷和掛起是倆個不同的概念,這是要注意的。
/*當一個執行緒呼叫Interrupt()方法的時候,那麼就會給這個執行緒設定一個標誌,表明這個執行緒已經被中斷了。* 但是,異常在被捕獲的時候,就會清理已經被中斷了的這個標誌。所以在catch子句中,在異常被捕獲的時候,這個標誌* 總是為假的。除了異常之外,這個標誌還可以用於其他的情況,比如執行緒可能會檢查它的中斷的狀態。*/
class Sleeper extends  Thread{
    private int duration;
    public Sleeper(String name, int sleepTime) {
        super(name);
duration
=sleepTime; start(); } @Override public void run() { try { // 當呼叫這個sleep()方法的時候,那麼這個執行緒可能會休眠指定的時間,但是也可能,在休眠的期間 // 還沒有夠的時候,就已經被中斷了,那麼try--catch就會捕獲這個異常。 sleep(duration); } catch (InterruptedException e) { System.out.println(getName() + "was interrupted." + "isInterrupted():"
+ isInterrupted()); return; } System.out.println(getName() + "has awakened"); } } class Joiner extends Thread{ private Sleeper sleeper; public Joiner(String name, Sleeper sleeper) { super(name); this.sleeper=sleeper; start(); } @Override public void run() { try { sleeper.join(); } catch (InterruptedException e) { System.out.println("Interrupted"); } System.out.println(getName() + "join completed"); } } public class Joining { public static void main(String[] args) { Sleeper sleepy = new Sleeper("Sleepy", 1500); Sleeper grumpy = new Sleeper("Grumpy", 1500); Joiner dopey = new Joiner("Dopey", sleepy); Joiner doc = new Joiner("Doc", grumpy); grumpy.interrupt(); } }
這個程式的輸出結果如下:
Grumpywas interrupted.isInterrupted():false
Docjoin completed
Sleepyhas awakened
Dopeyjoin completed
1.join的用法:如果有倆個執行緒A和B.在A執行緒中呼叫B.join().那麼當前執行緒,也就是A執行緒會被掛起,暫停執行。一直等到B執行完畢以後,A執行緒在繼續執行。
2.如果有倆個執行緒A和B。在A執行緒中呼叫B.join(1000).那麼當前執行緒,也就是A執行緒,就會被掛起1000ms.在A掛起的過程當中,B執行緒會執行。B執行緒執行了1000ms以後,A執行緒在繼續執行。
3呼叫Thread.sleep()方法的時候,如果當前執行緒處於中斷那狀態,那麼sleep()方法不會執行,同時會清除掉該狀態,並且丟擲interruptedException異常。上面的一個例子中說明的就是這個問題。
現在我們再來深入學習一下interrupt()方法。

interrupt字面上是中斷的意思,但在Java裡Thread.interrupt()方法實際上通過某種方式通知執行緒,並不會直接中止該執行緒。具體做什麼事情由寫程式碼的人決定,通常我們會中止該執行緒。

    如果執行緒在呼叫Object類的wait()、wait(long)或wait(long, int)方法,或者該類的 join() 、join(long) 、join(long, int) 、sleep(long) 或 sleep(long, int) 方法過程中受阻,則其中斷狀態將被清除,它還將收到一個 InterruptedException。

    如果該執行緒在可中斷的通道(java.nio.channels.InterruptibleChannel)上的 I/O 操作中受阻,則該通道將被關閉,該執行緒的中斷狀態將被設定並且該執行緒將收到一個 ClosedByInterruptException。

    如果該執行緒在一個 Selector (java.nio.channels.Selector) 中受阻,則該執行緒的中斷狀態將被設定,它將立即從選擇操作返回,並可能帶有一個非零值,就好像呼叫了選擇器的 wakeup 方法一樣。 

在java中,開啟一個多執行緒是很簡單的,只需要new一個runnable就可以了,但是要停止一個執行緒,卻不能簡單的使用Thread.stop()方法。

  首先來說說java中的中斷機制,Java中斷機制是一種協作機制,也就是說通過中斷並不能直接終止另一個執行緒,而需要被中斷的執行緒自己處理中斷。當呼叫interrupt()方法的時候,只是設定了要中斷執行緒的中斷狀態,而此時被中斷的執行緒的可以通過isInterrupted()或者是interrupted()方法判斷當前執行緒的中斷狀態是否標誌為中斷。我們可以從interrupt()方法來看:

  public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
        interrupt0();		// Just to set the interrupt flag
        b.interrupt();
        return;
        }
    }
    interrupt0();
    }

從這個方法中我們可以看到,最直接的呼叫時interrupt0()這個方法,而這個方法僅僅是設定了執行緒中斷狀態。

  我們再看看isInterrupted()方法:

  public boolean isInterrupted() {
    return isInterrupted(false);
    }

    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

從這個方法中,我們可以猜測到,isInterrupted()方法僅僅是檢查了當前執行緒的中斷狀態,但是不會清除這個狀態。

我們再來看看靜態方法interrupted()

 public static boolean interrupted() {
    return currentThread().isInterrupted(true);
    }

這個方法同樣是檢測當前執行緒的中斷狀態,但是這個方法會產生一個副作用,就是會清除當前執行緒的中斷狀態。

Thread.interrupt()  VS  Thread.stop()

 這兩個方法最大的區別在於:interrupt()方法是設定執行緒的中斷狀態,讓使用者自己選擇時間地點去結束執行緒;而stop()方法會在程式碼的執行處直接丟擲一個ThreadDeath錯誤,這是一個java.lang.Error的子類。所以直接使用stop()方法就有可能造成物件的不一致性。

呼叫Thread.sleep()方法的時候,如果當前執行緒處於中斷那狀態,那麼sleep()方法不會執行,同時會清除掉該狀態,並且丟擲interruptedException異常。

中斷的使用demo:

package com.app.basic;

public class InterruptTest {

    
    public static void main(String[] args) {
        

        Runnable runnable1 = new Runnable() {

            @Override
            public void run() {

                for (int i = 0; i < 10; i++) {

                    System.out.println(i);

                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("aa");
                        break;
                    }
                    try {
                        
                        Thread.sleep(1000);

                    } catch (InterruptedException e) {

                        e.printStackTrace();
                       //sleep方法丟擲這個異常之後會清除中斷狀態,所以需要重新設定中斷狀態
                        Thread.currentThread().interrupt();
                    }
                }
            }

        };

        final Thread t1 = new Thread(runnable1);

        Runnable runnable2 = new Runnable() {

            @Override
            public void run() {

                try {

                    Thread.sleep(3000);

                    t1.interrupt();

                } catch (InterruptedException e) {

                    e.printStackTrace();
                }

            }

        };

        Thread t2 = new Thread(runnable2);

        t1.start();
        t2.start();
    }

}
我們知道現在在concurrent類庫中,已經避免了在對Thread物件的直接的操作,而是通過Executor來進行操作。在我們已經啟動了執行緒以後,如果我們要終止執行緒的執行,那麼我們可以通過呼叫shutdownNow()方法來進行。那麼現在我們來聊一下這個方法是如何進行工作的。當你呼叫這個方法的時候,那麼就會發送一個interrupt()方法給所有的執行緒,當然這麼做是有意義的。現在我們來看一下各個方法之間是怎麼工作的。
package Concurrency;
public class InterruptTest {
    public static void main(String[] args) {
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("aa");
                        break;
                    }
                    try { 
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                       //sleep方法丟擲這個異常之後會清除中斷狀態,所以需要重新設定中斷狀態
                     Thread.currentThread().interrupt();
                    }
                }
            }
        };
        final Thread t1 = new Thread(runnable1);
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);//這裡的目的是先讓執行緒A執行一段時間。
                    t1.interrupt();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t2 = new Thread(runnable2);
        t1.start();
        t2.start();
    }
}
最後的輸0
1
2
java.lang.InterruptedException: sleep interrupted
3
aa
at java.lang.Thread.sleep(Native Method)
at Concurrency.InterruptTest$1.run(InterruptTest.java:24)
at java.lang.Thread.run(Unknown Source)出的結果為:
但是如果把try-catch中的內容清除掉的話,即就是下面的內容:
  try { 
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                       //sleep方法丟擲這個異常之後會清除中斷狀態,所以需要重新設定中斷狀態
                     //Thread.currentThread().interrupt();
                    }
那麼最後的輸出的結果為:
0
1
2
java.lang.InterruptedException: sleep interrupted
3
at java.lang.Thread.sleep(Native Method)
at Concurrency.InterruptTest$1.run(InterruptTest.java:24)
at java.lang.Thread.run(Unknown Source)
4
5
6
7
8
9
如果把run中的內容改為如下的內容的話,那麼輸出的結果又會是怎麼樣的呢?
 Runnable runnable1 = new Runnable() {


            @Override
            public void run() {


                for (int i = 0; i < 10; i++) {


                    System.out.println(i);

//這裡做了小小的改變。如果呼叫的是Thread.interrupted()這個方法的話,那麼將會產生的一個副作用就是
//這個將會清除執行緒的中斷的狀態。
                    if (Thread.interrupted()) {
                        System.out.println("aa");
                        break;
                    }
                    try {
                        
                        Thread.sleep(1000);


                    } catch (InterruptedException e) {


                        e.printStackTrace();
                       //sleep方法丟擲這個異常之後會清除中斷狀態,所以需要重新設定中斷狀態
                     //Thread.currentThread().interrupt();
                    }
                }
            }


        };
最後的輸出的結果為:
0
1
2
java.lang.InterruptedException: sleep interrupted3


at java.lang.Thread.sleep(Native Method)
at Concurrency.InterruptTest$1.run(InterruptTest.java:24)
at java.lang.Thread.run(Unknown Source)
4
5
6
7
8
9
一般來說,當我們啟動執行緒的時候,我們一般會使用某個Executor來進行啟動,但是如果我們通過shutDownNow()方法來關閉執行緒的話,那麼Executro執行的所有的任務都會被中斷。如果我們只是希望中斷其中的一個任務,那麼我們在執行任務的時候,就應該使用submit()來執行。而不是Executor(),否則是不會成功的。
其實有些操作通過interrupte()這個方法是不能終止的。比如像IO的操作或者是Synchronized()塊。下面我們來看一下這樣的操作。
/*這個程式證明了IOSynchronized()塊中的等待是不可以中斷的。
但是其實通過程式的瀏覽,我們也可以知道無論是IO嘗試* 還是synchronized()中的內容,我們都不需要任何的Interrupted()的處理器。*/
//這是可以中斷的阻塞的例項
class SleepBlocked implements  Runnable{
    @Override
public void run() {
        try {
            TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
            System.out.println("InterruptedEception");
}
        System.out.println("Exiting SleepBlocked.run()");
}
}
//這是不可以中斷的阻塞的例項
class IOBlocked implements  Runnable{
    private InputStream in;
    public IOBlocked(InputStream is) {
        in=is;
}
    @Override
public void run() {
        System.out.println("waiting for read");
        try {
            in.read();
} catch (IOException e) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted from blocked I/O");
}else {
                throw new RuntimeException(e);
}
        }
        System.out.println("Exiting IoBloced().run");
}
}
//這是不可以中斷的阻塞的例項
/*在這裡我們為了演示synchronizedBlock,我們必須首先獲取鎖。
這是通過在構造器中建立了匿名的Thread()內部類來實現的。* 這個匿名的內部類通過呼叫f()方法獲取了物件鎖。* 由於驅動SynchronizedBlock()方法中的run()方法中的鎖匿名內部類中的鎖* 是一樣的。但是由於這個方法永遠都不會釋放鎖。* SynchronizedBlocj()中的run()方法試圖在呼叫f().並且阻塞等待這個* 鎖被釋放。*/
class SynchronizedBlocked implements Runnable{
    
    public synchronized void f() {
        while (true) {
            Thread.yield();//這裡需要注意的問題是:這個yield()方法和sleep()方法都是一樣的。這倆個方法在
//阻塞的時候,都是不會釋放鎖的。
}
    }
    
    public SynchronizedBlocked() {
        //在構造器中啟動一個執行緒。
new Thread(){
            @Override
public void run() {
                f();
}
        }.start();
}
    
    @Override
public void run() {
        System.out.println("Trying to call f()");
f();
System.out.println("Exiting SynchronizedBlocked.run()");
}
}

public class Interrupting {
    private static ExecutorService exec = Executors.newCachedThreadPool();
    public static void test(Runnable r) throws InterruptedException {
        Future<?> f = exec.submit(r);
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("Interrupting " + r.getClass().getName());
f.cancel(true);
System.out.println("Interrupt sent to " + r.getClass().getName());
}

    public static void main(String[] args) throws  Exception{
        test(new SleepBlocked());
test(new IOBlocked(System.in));
test(new SynchronizedBlocked());
TimeUnit.SECONDS.sleep(3);
System.out.println("Abroting with Systm.exit(0)");
System.exit(0);
}
}
最後的輸出的結果為:
Interrupting Concurrency.SleepBlocked
Interrupt sent to Concurrency.SleepBlocked
InterruptedEception
Exiting SleepBlocked.run()
waiting for read
Interrupting Concurrency.IOBlocked
Interrupt sent to Concurrency.IOBlocked
Trying to call f()
Interrupting Concurrency.SynchronizedBlocked
Interrupt sent to Concurrency.SynchronizedBlocked
Abroting with Systm.exit(0)
從輸出的結果中,我們可以看出來,你能夠中斷對sleep()的呼叫(或者任何要求丟擲InterruptedException()的呼叫。但是,你不能中斷正在試圖獲取synchronized鎖或者試圖嘗試執行IO操作的執行緒。這有點令人煩惱,特別是在建立執行I/O的任務的時候,因為這意味這I/O具有鎖住你的多執行緒序的潛在的可能。特別是對基於Web的程式,這更是關乎利害。這就是說,你不能中斷IO的相關的操作,這就是說IO操作有可能鎖住多執行緒。
那麼如何來避免這個問題呢?我們可以通過關閉底層資源的方法來避免這個問題的產生。
/*我們通過關閉底層資源的方法可以來解決IO操作可以鎖住多執行緒的潛在的可能性*/
public class CloseResourse {
    public static void main(String[] args) throws  Exception{
        ExecutorService exec= Executors.newCachedThreadPool();
ServerSocket server = new ServerSocket(8080);
InputStream socketInput=new Socket("localhost",8080).getInputStream();
exec.execute(new IOBlocked(socketInput));
exec.execute(new IOBlocked(System.in));
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("Shutting down all threads");
exec.shutdownNow();
TimeUnit.SECONDS.sleep(1);
System.out.println("Closing " + socketInput.getClass().getName());
socketInput.close();
TimeUnit.SECONDS.sleep(1);
System.out.println("Closing" + System.in.getClass().getName());
System.in.close();
}
}
最後的輸出的結果為:waiting for read
waiting for read
Shutting down all threads
Closing java.net.SocketInputStream
Interrupted from blocked I/O
Exiting IoBloced().run
Closingjava.io.BufferedInputStream
在shutdownNow()被呼叫以後以及在倆個輸入流上呼叫Close()之前的延遲強調的是一旦底層資源被關閉,任務就會解除阻塞了。請注意,有一點很有趣的是,interrupt()看起來發生在關閉socket而不是在關閉System.in的時刻。從輸出的結果中
Interrupted from blocked I/O,我們也可以看到,這時候執行緒就可以被中斷了。
java中的IO類和NIO類是倆個不同的內容。NIO類可以說是IO類的增強版。與普通的IO類不同的是,各種NIO類都提供了更人性化的IO中斷。被阻塞的nio通道會自動地響應中斷。如你所見的那樣,你還可以關閉底層的資源以釋放鎖。注意,使用execute()來啟動倆個任務的時候,並且呼叫e.shutdownNow()將可以很容易地終止所有的事物,而對於捕獲上例中的Future,只有在將中斷髮給一個執行緒,同時不發給另外一個執行緒的時候,才會用得到。
下面我們在來看一下關於interrupt()的小的例子。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


class LiftOff implements Runnable {
  protected int countDown = 5000;
  private static int taskCount;
  private final int id = taskCount++;
  public LiftOff() {}
  public LiftOff(int countDown) {
    this.countDown = countDown;
  }
  public String status() {
    return "#" + id + "(" +
      (countDown > 0 ? countDown : "Liftoff!") + "), ";
  }
  public void run() {
    while(countDown-- > 0) {
      if(Thread.currentThread().isInterrupted()) {
        System.out.println("Interrupted: #" + id);
        return;
      }
      System.out.print(status());
      Thread.yield();//這個並不是使執行緒進入阻塞狀態。這是使執行緒進入runnable狀態。失去了對cpu時間片的控制
      
    }
  }
}


public class E20_InterruptCachedThreadPool {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService exec = Executors.newCachedThreadPool();
    //這裡要看清楚的一點是for迴圈是的形式。是沒有將 Thread.yield()和
    //Thread.sleep(1000)包括進來的。
    for(int i = 0; i < 5; i++)
      exec.execute(new LiftOff());
     Thread.yield();//讓執行緒執行一段時間。
    exec.shutdownNow();
  }
}
最後的輸出的結果為:
Interrupted: #4
#2(4999), #0(4999), #1(4999), Interrupted: #3
Interrupted: #2
Interrupted: #0
Interrupted: #1
可是如果我們進行小小的修改,那麼輸出的結果就會是另外的一個樣:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


class LiftOff22 implements Runnable {
  protected int countDown = 5000;
  private static int taskCount;
  private final int id = taskCount++;
  public LiftOff22() {}
  public LiftOff22(int countDown) {
    this.countDown = countDown;
  }
  public String status() {
    return "#" + id + "(" +
      (countDown > 0 ? countDown : "Liftoff!") + "), ";
  }
  public void run() {
    while(countDown-- > 0) {
      System.out.print(status());
      Thread.yield();//這個並不是使執行緒進入阻塞狀態。
    }
  }
}


public class E20 {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService exec = Executors.newCachedThreadPool();
    //這裡要看清楚的一點是for迴圈是的形式。是沒有將 Thread.yield()和
    //Thread.sleep(1000)包括進來
    for(int i = 0; i < 5; i++)
      exec.execute(new LiftOff22());
     Thread.yield();//讓執行緒執行一段時間。
    exec.shutdownNow();
  }
}
在這裡,當我們呼叫shutdownNow()這個方法的時候,那麼這個方法會給每一個執行緒都發送一個interrupt()的標誌。如果執行緒是處於阻塞狀態的話,那麼這個執行緒就會被中斷。但是如果這個執行緒不是處於阻塞狀態的話,那麼這個執行緒就不會被中斷。這是要注意的。注意呼叫yield()這個方法並不是使這個執行緒處於阻塞的狀態,只是使這個執行緒處於runnable狀態。當這個執行緒再次獲得cpu時間片的話,那麼這個執行緒就會再次執行。這是要注意的。
下面我們再來看一個例子:
class NonTask {
  static void longMethod() throws InterruptedException {
    TimeUnit.SECONDS.sleep(60);  // Waits one minute
  }
}


class Task implements Runnable {
  public void run() {
    try {
      NonTask.longMethod();
    } catch (InterruptedException ie) {
      System.out.println(ie.toString());
    } finally {
      // Any cleanup code here...
    }
  }
}
class Task2 implements Runnable{


@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
System.out.println(i);
//TimeUnit.MICROSECONDS.sleep(500);
}

}

}


public class E18_Interruption {
  public static void main(String[] args) throws Exception {
    Thread t = new Thread(new Task());
    t.start();
    TimeUnit.SECONDS.sleep(1);
    t.interrupt();
   
  }
}
最後的輸出的結果為:
java.lang.InterruptedException: sleep interrupted
說明這個執行緒是可以被中斷的。
現在我們來看一下被互斥所阻塞的例子:
如果你嘗試著在一個物件上呼叫其synchronized方法,而這個物件的鎖已經被其它的任務獲得,那麼呼叫任務將會被掛起(阻塞),直至這個鎖可以被獲得為止。下面的示例中說明了,同一個互斥可以如何被同一個任務多次獲得。
public class MultiBlock {
public synchronized  void f1(int count){
if(--count>0){
System.out.println("f1() calling f2() with count"+count);
f2(count);
}
}

public synchronized void f2(int count){
if(--count>0){
System.out.println("f2() calling f1() with count"+count);
f1(count);
}
}

public static void main(String[] args){
final MultiBlock mutiBlock=new MultiBlock();
new Thread(){
public void run(){
mutiBlock.f1(10);
}
}.start();

}
}
最後的輸出的結果為:
f1() calling f2() with count9
f2() calling f1() with count8
f1() calling f2() with count7
f2() calling f1() with count6
f1() calling f2() with count5
f2() calling f1() with count4
f1() calling f2() with count3
f2() calling f1() with count2
f1() calling f2() with count1
在main中建立了一個呼叫f1()的Thread.然後f1()和f2()互相呼叫直至count變為0.由於這個任務在第一次對f1()的呼叫中已經獲得了multiBlock物件鎖。因此在同一個任務中將在對f2()的呼叫中再次獲得這個鎖。以此類推。這麼做是有意義的。因為一個任務應該能夠呼叫在同一個物件中的其它的synchronized方法。而這個任務已經持有鎖了。
在這裡我想再強調的一點就是,當你線上程上呼叫interrupt()的時候,中斷髮生的唯一時刻是在任務要進入到阻塞操作中,或者已經在阻塞操作內部時(如你所見,除了不可中斷的IO或者被阻塞的synchronized方法外,在其餘的情況下,你無所事事)。因此如果你呼叫interrupt()以停止某個任務的時候,如果在run()迴圈中碰巧沒有產生阻塞的情況下呼叫,那麼我們就可以通過檢查中斷的方法來進行退出。