如何正確停止java中的執行緒
為什麼不能使用Thread.stop()方法?
從SUN的官方文件可以得知,呼叫Thread.stop()方法是不安全的,這是因為當呼叫Thread.stop()方法時,會發生下面兩件事:
1. 即刻丟擲ThreadDeath異常,線上程的run()方法內,任何一點都有可能丟擲ThreadDeath Error,包括在catch或finally語句中。
2. 釋放該執行緒所持有的所有的鎖
當執行緒丟擲ThreadDeath異常時,會導致該執行緒的run()方法突然返回來達到停止該執行緒的目的。ThreadDetath異常可以在該執行緒run()方法的任意一個執行點丟擲。但是,執行緒的stop()方法一經呼叫執行緒的run()方法就會即刻返回嗎?
- publicstaticvoid main(String[] args) {
- try {
- Thread t = new Thread() {
- publicsynchronizedvoid run() {
- try {
- long start=System.currentTimeMillis();
- for (int i = 0; i < 100000; i++)
- System.out.println("runing.." + i);
- System.out.println((System.currentTimeMillis()-start)/
- } catch (Throwable ex) {
- System.out.println("Caught in run: " + ex);
- ex.printStackTrace();
- }
- }
- };
- t.start();
- // Give t time to get going...
- Thread.sleep(
- t.stop(); // EXPECT COMPILER WARNING
- } catch (Throwable t) {
- System.out.println("Caught in main: " + t);
- t.printStackTrace();
- }
- }
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程式碼- publicstaticvoid main(String[] args) {
- final Object lock = new Object();
- try {
- Thread t0 = new Thread() {
- publicvoid 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() {
- publicvoid 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();
- }
- }
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 } }