1. 程式人生 > >如何正確停止java中的執行緒

如何正確停止java中的執行緒

為什麼不能使用Thread.stop()方法?

從SUN的官方文件可以得知,呼叫Thread.stop()方法是不安全的,這是因為當呼叫Thread.stop()方法時,會發生下面兩件事:

1. 即刻丟擲ThreadDeath異常,線上程的run()方法內,任何一點都有可能丟擲ThreadDeath Error,包括在catch或finally語句中。

2. 釋放該執行緒所持有的所有的鎖

當執行緒丟擲ThreadDeath異常時,會導致該執行緒的run()方法突然返回來達到停止該執行緒的目的。ThreadDetath異常可以在該執行緒run()方法的任意一個執行點丟擲。但是,執行緒的stop()方法一經呼叫執行緒的run()方法就會即刻返回嗎?

Java程式碼 複製程式碼 收藏程式碼
  1. publicstaticvoid main(String[] args) {   
  2. try {   
  3.             Thread t = new Thread() {   
  4. publicsynchronizedvoid run() {   
  5. try {   
  6. long start=System.currentTimeMillis();   
  7. for (int i = 0; i < 100000; i++)   
  8.                             System.out.println("runing.." + i);   
  9.                         System.out.println((System.currentTimeMillis()-start)/
    1000);   
  10.                     } catch (Throwable ex) {   
  11.                         System.out.println("Caught in run: " + ex);   
  12.                         ex.printStackTrace();   
  13.                     }   
  14.                 }   
  15.             };   
  16.             t.start();   
  17. // Give t time to get going...
  18.             Thread.sleep(
    100);   
  19.             t.stop(); // EXPECT COMPILER WARNING
  20.         } catch (Throwable t) {   
  21.             System.out.println("Caught in main: " + t);   
  22.             t.printStackTrace();   
  23.         }   
  24.     }  
public static void main(String[] args) {
		try {
			Thread t = new Thread() {
				public synchronized void run() {
					try {
						long start=System.currentTimeMillis();
						for (int i = 0; i < 100000; i++)
							System.out.println("runing.." + i);
						System.out.println((System.currentTimeMillis()-start)/1000);
					} catch (Throwable ex) {
						System.out.println("Caught in run: " + ex);
						ex.printStackTrace();
					}
				}
			};
			t.start();
			// Give t time to get going...
			Thread.sleep(100);
			t.stop(); // EXPECT COMPILER WARNING
		} catch (Throwable t) {
			System.out.println("Caught in main: " + t);
			t.printStackTrace();
		}

	}

假設我們有如上一個工作執行緒,它的工作是數數,從1到1000000,我們的目標是在它進行數數的過程中,停止該執行緒的運作。如果我們按照上面的方式來呼叫thread.stop()方法,原則上是可以實現我們的目標的,根據SUN官方文件的解釋,加上在上面的程式中,主執行緒只休眠了100ms,而工作執行緒從1數到1000000所花時間大概是4-5s,那麼該工作執行緒應該只從1數到某個值(小於1000000),然後執行緒停止。 

但是根據執行結果來看,並非如此。

結果:

。。。

runing..99998

runing..99999

5

。。。

runing..99998

runing..99999

4

每次執行的結果都表明,工作執行緒並沒有停止,而是每次都成功的數完數,然後正常中止,而不是由stop()方法進行終止的。這個是為什麼呢?根據SUN的文件,原則上只要一呼叫thread.stop()方法,那麼執行緒就會立即停止,並丟擲ThreadDeath error,查看了Thread的原始碼後才發現,原先Thread.stop0()方法是同步的,而我們工作執行緒的run()方法也是同步,那麼這樣會導致主執行緒和工作執行緒共同爭用同一個鎖(工作執行緒物件本身),由於工作執行緒在啟動後就先獲得了鎖,所以無論如何,當主執行緒在呼叫t.stop()時,它必須要等到工作執行緒的run()方法執行結束後才能進行,結果導致了上述奇怪的現象。

把上述工作執行緒的run()方法的同步去掉,再進行執行,結果就如上述第一點描述的那樣了

可能的結果:


runing..4149
runing..4150
runing..4151
runing..4152runing..4152Caught in run: java.lang.ThreadDeath

或者

runing..5245
runing..5246
runing..5247
runing..5248runing..5248Caught in run: java.lang.ThreadDeath

接下來是看看當呼叫thread.stop()時,被停止的執行緒會不會釋放其所持有的鎖,看如下程式碼:

Java程式碼 複製程式碼 收藏程式碼
  1. publicstaticvoid main(String[] args) {   
  2. final Object lock = new Object();   
  3. try {   
  4.             Thread t0 = new Thread() {   
  5. publicvoid run() {   
  6. try {   
  7. synchronized (lock) {   
  8.                             System.out.println("thread->" + getName()   
  9.                                     + " acquire lock.");   
  10.                             sleep(3000);// sleep for 3s
  11.                             System.out.println("thread->" + getName()   
  12.                                     + " release lock.");   
  13.                         }   
  14.                     } catch (Throwable ex) {   
  15.                         System.out.println("Caught in run: " + ex);   
  16.                         ex.printStackTrace();   
  17.                     }   
  18.                 }   
  19.             };   
  20.             Thread t1 = new Thread() {   
  21. publicvoid run() {   
  22. synchronized (lock) {   
  23.                         System.out.println("thread->" + getName()   
  24.                                 + " acquire lock.");   
  25.                     }   
  26.                 }   
  27.             };   
  28.             t0.start();   
  29. // Give t time to get going...
  30.             Thread.sleep(100);   
  31. //t0.stop();
  32.             t1.start();   
  33.         } catch (Throwable t) {   
  34.             System.out.println("Caught in main: " + t);   
  35.             t.printStackTrace();   
  36.         }   
  37.     }  
public static void main(String[] args) {
		final Object lock = new Object();
		try {
			Thread t0 = new Thread() {
				public void run() {
					try {
						synchronized (lock) {
							System.out.println("thread->" + getName()
									+ " acquire lock.");
							sleep(3000);// sleep for 3s
							System.out.println("thread->" + getName()
									+ " release lock.");
						}
					} catch (Throwable ex) {
						System.out.println("Caught in run: " + ex);
						ex.printStackTrace();
					}
				}
			};

			Thread t1 = new Thread() {
				public void run() {
					synchronized (lock) {
						System.out.println("thread->" + getName()
								+ " acquire lock.");
					}
				}
			};

			t0.start();
			// Give t time to get going...
			Thread.sleep(100);
			//t0.stop();
			t1.start();
		} catch (Throwable t) {
			System.out.println("Caught in main: " + t);
			t.printStackTrace();
		}

	}

當沒有進行t0.stop()方法的呼叫時, 可以發現,兩個執行緒爭用鎖的順序是固定的。

輸出:

thread->Thread-0 acquire lock.
thread->Thread-0 release lock.
thread->Thread-1 acquire lock.

但呼叫了t0.stop()方法後,(去掉上面的註釋//t0.stop();),可以發現,t0執行緒丟擲了ThreadDeath error並且t0執行緒釋放了它所佔有的鎖。

輸出:

thread->Thread-0 acquire lock.
thread->Thread-1 acquire lock.
Caught in run: java.lang.ThreadDeath
java.lang.ThreadDeath
 at java.lang.Thread.stop(Thread.java:715)
 at com.yezi.test.timeout.ThreadStopTest.main(ThreadStopTest.java:40)

從上面的程式驗證結果來看,thread.stop()確實是不安全的。它的不安全主要是針對於第二點:釋放該執行緒所持有的所有的鎖。一般任何進行加鎖的程式碼塊,都是為了保護資料的一致性,如果在呼叫thread.stop()後導致了該執行緒所持有的所有鎖的突然釋放,那麼被保護資料就有可能呈現不一致性,其他執行緒在使用這些被破壞的資料時,有可能導致一些很奇怪的應用程式錯誤。

如何正確停止執行緒

關於如何正確停止執行緒,這篇文章(how to stop thread)給出了一個很好的答案, 總結起來就下面3點(在停止執行緒時):

1. 使用violate boolean變數來標識執行緒是否停止

2. 停止執行緒時,需要呼叫停止執行緒的interrupt()方法,因為執行緒有可能在wait()或sleep(), 提高停止執行緒的即時性

3. 對於blocking IO的處理,儘量使用InterruptibleChannel來代替blocking IO

核心如下:

If you are writing your own small thread then you should follow the following example code.

    private volatile Thread myThread;
    public void stopMyThread() {
        Thread tmpThread = myThread;
        myThread = null;
        if (tmpThread != null) {
            tmpThread.interrupt();
        }
    }
    public void run() {
        if (myThread == null) {
           return; // stopped before started.
        }
        try {
            // all the run() method's code goes here
            ...
            // do some work
            Thread.yield(); // let another thread have some time perhaps to stop this one.
            if (Thread.currentThread().isInterrupted()) {
               throw new InterruptedException("Stopped by ifInterruptedStop()");
            }
            // do some more work
            ...
        } catch (Throwable t) {
           // log/handle all errors here
        }
    }