1.停止線程
停止線程不像停止一個循環break一樣干脆。
停止一個線程意味着在線程處理完任務之前停掉正在做的操作,也就是放棄當前的操作。雖然看起來簡單,但是必須做好正確的防范措施,以便達到預期的效果。停止一個線程可以用Thread.stop(),但最好不要用它。雖然它確實可以停止一個正在運行的線程,但是這個方法是不安全的,而且已經作廢的方法。
大多數停止一個線程用Thread.interrupt()方法,盡管方法的名稱是"中止,停止"的意思,但這個方法不一定會停止一個正在運行的線程,還需要加入一個判斷才可以完成線程的停止。
在Java中有3種方法可以停止正在運行的線程:
(1)使用退出標志使線程正常終止,也就是當run方法完成后線程終止。
(2)使用stop方法強行終止線程,但是不推薦使用這個方法,因為stop和suspend、resume一樣,都是過期作廢的方法。
(3)使用interrupt方法中斷線程。
1.停止不了的線程
調用thread.interrupt()方法,但是此方法並不會馬上停止線程,只是在當前線程打了一個停止的標記,並不是真正的停止線程。
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo1 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo1.class); @Override public void run() { for (int i = 0; i < 50;) { log.debug("i->{}", i++); } } public static void main(String[] args) { try { Demo1 demo1 = new Demo1(); demo1.start(); Thread.sleep(200); // 發出中斷線程信號 demo1.interrupt(); } catch (InterruptedException e) { log.error("main catch ", e); } } }
結果:(仍然會執行完線程的run方法,也就是interrupt()方法並沒有中斷線程)
2.判斷線程是否終止
在Java的SDK種,提供了兩個方法判斷線程是否終止:
(1)this.interrupted(),測試當前線程是否已經中止 (靜態方法------測試當前線程是否已經是中斷狀態,執行后具有將狀態標志清除為false的功能)
public static boolean interrupted() { return currentThread().isInterrupted(true); }
(2)this.isInterrupted(),測試線程是否已經中斷。(實例方法-----測試線程對象Thread對象是否已經是中斷狀態,但不清除狀態標志)
public boolean isInterrupted() { return isInterrupted(false); }
可以看出上面兩個方法都調用了isInterrupted(boolean)方法,查看源碼:(參數是是否重置狀態)
/** * 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);
- this.interrupted()靜態方法的研究--執行任何線程的此方法都相當於執行Thread.currentThread.interrupted(),會返回線程的中斷狀態並且會清除中斷狀態
測試當前線程是否已經中斷,當前線程是指運行this.interrupted()方法的線程。
測試代碼:
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo2 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo2.class); @Override public void run() { for (int i = 0; i < 5000000;) { } } public static void main(String[] args) { try { Demo2 demo2 = new Demo2(); demo2.start(); Thread.sleep(1000); // 發出中斷線程信號 demo2.interrupt(); log.debug("是否停止1?{}", demo2.interrupted()); log.debug("是否停止2?{}", demo2.interrupted()); } catch (InterruptedException e) { log.error("main catch ", e); } log.debug("end"); } }
結果:
2018-12-07 [cn.qlq.thread.three.Demo2]-[DEBUG] 是否停止1?false
2018-12-07 [cn.qlq.thread.three.Demo2]-[DEBUG] 是否停止2?false
2018-12-07 [cn.qlq.thread.three.Demo2]-[DEBUG] end
解釋:雖然調用的是demo2.interrupted()方法,但是我們從源碼也可以看出測的是當前線程,當前線程就是執行當前代碼的線程,也就是main線程,所以打印了兩個false。
如何測試main線程的中斷效果:
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo3 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo3.class); public static void main(String[] args) { Thread.currentThread().interrupt(); // 發出中斷線程信號 log.debug("是否停止1?{}", Thread.interrupted()); log.debug("是否停止2?{}", Thread.interrupted()); } }
結果:
2018-12-07 [cn.qlq.thread.three.Demo3]-[DEBUG] 是否停止1?true
2018-12-07 [cn.qlq.thread.three.Demo3]-[DEBUG] 是否停止2?false
第二個參數是返回值是false的原因?官網對此方法的解釋:
測試當前線程是否已經中斷(當前線程是指執行當前代碼的線程)。線程的中斷狀態由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將返回false(在第一次調用已清除了其中狀態之后,且第二次調用檢驗完中斷狀態前,當前線程再次中斷的情況除外)。
解釋已經很清楚了,interrupted()方法具有清除狀態的功能,所以第二次調用返回的是false。
- isInterrupyed()方法的研究-----檢測線程對象是否中斷的狀態,並且不會清除狀態。
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo5 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo5.class); @Override public void run() { for (int i = 0; i < 5000000;) { } } public static void main(String[] args) { try { Demo5 demo2 = new Demo5(); demo2.start(); Thread.sleep(1000); // 發出中斷線程信號 log.debug("是否停止0?{}", demo2.isInterrupted()); demo2.interrupt(); log.debug("是否停止1?{}", demo2.isInterrupted()); log.debug("是否停止2?{}", demo2.isInterrupted()); } catch (InterruptedException e) { log.error("main catch ", e); } log.debug("end"); } }
結果:
2018-12-07 [cn.qlq.thread.three.Demo5]-[DEBUG] 是否停止0?false
2018-12-07 [cn.qlq.thread.three.Demo5]-[DEBUG] 是否停止1?true
2018-12-07 [cn.qlq.thread.three.Demo5]-[DEBUG] 是否停止2?true
2018-12-07 [cn.qlq.thread.three.Demo5]-[DEBUG] end
3.能停止的線程 --- 異常法
在for循環種檢測是否收到中斷信號,收到中斷信號就break。
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * for循環檢測是否是停止狀態,如果是停止狀態就不執行后面代碼 * * @author Administrator * */ public class Demo4 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo4.class); @Override public void run() { super.run(); for (int i = 0; i < 50; i++) { if (Thread.interrupted()) { log.debug("線程已經終止,我要退出"); break; } log.debug("i={}", i); } } public static void main(String[] args) { try { Demo4 demo4 = new Demo4(); demo4.start(); Thread.sleep(3); // 發出中斷信號 demo4.interrupt(); } catch (InterruptedException e) { log.error("InterruptedException ", e); } log.debug("end"); } }
結果:
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] i=0
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] i=1
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] end
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] 線程已經終止,我要退出
上面有個問題,如果for循環后面有語句還是會執行后面的語句,解決辦法如下:(思路就是合理的利用異常機制)
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * for循環檢測是否是停止狀態,如果是停止狀態就不執行后面代碼 * * @author Administrator * */ public class Demo6 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo4.class); @Override public void run() { super.run(); try { for (int i = 0; i < 50; i++) { if (Thread.interrupted()) { log.debug("線程已經終止,我要退出"); // 拋出異常 throw new InterruptedException(); } log.debug("i={}", i); } log.debug("for 后面的語句"); } catch (InterruptedException e) { log.error("進入catch方法---", e); } } public static void main(String[] args) { try { Demo6 demo6 = new Demo6(); demo6.start(); Thread.sleep(3); // 發出中斷信號 demo6.interrupt(); } catch (InterruptedException e) { log.error("InterruptedException ", e); } log.debug("end"); } }
結果:
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] i=0
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] end
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] 線程已經終止,我要退出
2018-12-07 [cn.qlq.thread.three.Demo4]-[ERROR] 進入catch方法---
java.lang.InterruptedException
at cn.qlq.thread.three.Demo6.run(Demo6.java:24)
4.在沉睡種停止
如果線程在sleep中中斷,會是什么效果?
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 沉睡中中斷會進捕捉到中斷異常 * * @author Administrator * */ public class Demo7 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo4.class); @Override public void run() { super.run(); try { Thread.sleep(50 * 1000); } catch (InterruptedException e) { log.error("進入catch方法"); log.info("demo7.isInterrupted 2-> {}", this.isInterrupted()); } } public static void main(String[] args) { Demo7 demo7 = new Demo7(); demo7.start(); // 發出中斷信號 demo7.interrupt(); log.info("demo7.isInterrupted 1-> {}", demo7.isInterrupted()); log.debug("end"); } }
結果:
2018-12-07 [cn.qlq.thread.three.Demo4]-[ERROR] 進入catch方法
2018-12-07 [cn.qlq.thread.three.Demo4]-[INFO] demo7.isInterrupted 1-> true
2018-12-07 [cn.qlq.thread.three.Demo4]-[INFO] demo7.isInterrupted 2-> false
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] end
可以看出線程在sleep狀態下中斷某一線程會進入catch語句,並且清除停止狀態值,使之變為false。(這里也就解釋了為什么sleep(long)拋出一個檢查型異常(InterruptedException))。
上面代碼是先中斷后sleep,其實先interrupt后sleep也是一樣的錯誤,如下:
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 先中斷后睡眠也會進入異常 * * @author Administrator * */ public class Demo8 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo4.class); public static void main(String[] args) { // 發出中斷信號 Thread.currentThread().interrupt(); try { Thread.sleep(50000); } catch (InterruptedException e) { log.debug("interruptedException", e); } } }
結果:
2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] interruptedException
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.qlq.thread.three.Demo8.main(Demo8.java:20)
5.能停止的線程------stop 暴力停止(釋放鎖,容易造成數據不一致)
源碼如下:
@Deprecated public final void stop() { stop(new ThreadDeath()); } @Deprecated public final synchronized void stop(Throwable obj) { if (obj == null) throw new NullPointerException(); SecurityManager security = System.getSecurityManager(); if (security != null) { checkAccess(); if ((this != Thread.currentThread()) || (!(obj instanceof ThreadDeath))) { security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); } } // A zero status value corresponds to "NEW", it can't change to // not-NEW because we hold the lock. if (threadStatus != 0) { resume(); // Wake up thread if it was suspended; no-op otherwise } // The VM can handle all thread states stop0(obj); } private native void stop0(Object o);
使用stop停止線程是非常暴力的。
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * stop暴力停止 * * @author Administrator * */ public class Demo9 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo9.class); @Override public void run() { int i = 0; while (true) { log.debug("{}", i++); } } public static void main(String[] args) throws InterruptedException { Demo9 demo9 = new Demo9(); demo9.start(); Thread.sleep(100); // 暴力停止 demo9.stop(); log.debug("end"); } }
結果:
可以看到直接殺死線程,解除由該線程獲得的所有對象鎖頭,而且可能使對象處於不連貫狀態,如果其他線程訪問對象,而導致的錯誤很難檢查。所以被廢棄。
調用stop()方法時會拋出java.lang.ThreadDeath,但是在通常情況下,此異常不需要顯示的捕捉。(在JDK7中已經沒有拋出異常了,查看上面源碼也可以知道)
方法stop()已經作廢,因為如果強制性讓一個線程停止則有可能使一些清理性的工作得不到完成。另外的情況就是對鎖定的對象進行了"解鎖",導致數據得不到同步的處理,出現數據不一致的情況。
- 使用stop釋放鎖的不良后果:
使用stop釋放鎖會造成數據不一致的情況。如果出現這樣的情況,程序處理的數據就可能遭到破壞,最終導致程序執行的錯誤。
例如:(此處需要理解引用類型)
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * stop暴力停止釋放鎖造成的數據不一致 * * @author Administrator * */ public class Demo10 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo10.class); private SyncObj syncObj; public Demo10(SyncObj syncObj) { this.syncObj = syncObj; } @Override public void run() { syncObj.setValue("b", "bbb"); } public static void main(String[] args) throws InterruptedException { SyncObj syncObj = new SyncObj(); Demo10 demo9 = new Demo10(syncObj); demo9.start(); Thread.sleep(5 * 1000); // 暴力停止 demo9.stop(); log.debug("syncObj - > {}", syncObj); } } class SyncObj { private String username = "a"; private String password = "aaa"; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public synchronized void setValue(String username, String password) { try { this.username = username; // 休眠10秒中 Thread.sleep(10 * 1000); this.password = password; } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "SyncObj [username=" + username + ", password=" + password + "]"; } }
結果:(由於停止線程造成的數據不一致,只修改了username,沒有修改密碼)
2018-12-07 [cn.qlq.thread.three.Demo10]-[DEBUG] syncObj - > SyncObj [username=b, password=aaa]
6.使用return停止線程
將interrupt與return結合也可以很好的實現停止線程的效果。
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * return + interrupt結束線程 * * @author Administrator * */ public class Demo11 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo11.class); @Override public void run() { while (true) { if (this.isInterrupted()) { log.debug("run收到終止信號,終止了!"); return; } log.debug("sss"); } } public static void main(String[] args) throws InterruptedException { // 暴力停止 Demo11 demo11 = new Demo11(); demo11.start(); Thread.sleep(100); demo11.interrupt(); } }
結果:
建議使用拋異常的方法來實現線程的停止,因為在catch中還可以向上拋,使線程傳播的事件得以傳播。
2.暫停線程
暫停線程意味着可以恢復運行。在Java多線程編程中,可以使用suspend()方法暫停線程,使用resume()恢復線程。這兩個方法都是過期作廢的方法。
源碼如下:
@Deprecated public final void suspend() { checkAccess(); suspend0(); } @Deprecated public final void resume() { checkAccess(); resume0(); } private native void setPriority0(int newPriority); private native void stop0(Object o); private native void suspend0(); private native void resume0(); private native void interrupt0(); private native void setNativeName(String name);
1.suspend()和resume()的使用
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo12 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo12.class); private long i; @Override public void run() { while (true) { i++; } } public static void main(String[] args) throws InterruptedException { // 暴力停止 Demo12 demo12 = new Demo12(); demo12.start(); // A段 Thread.sleep(500); demo12.suspend(); log.debug("time1->{},i->{}", System.currentTimeMillis(), demo12.getI()); Thread.sleep(500); log.debug("time2->{},i->{}", System.currentTimeMillis(), demo12.getI()); // B段 demo12.resume(); Thread.sleep(500); // C段 Thread.sleep(500); demo12.suspend(); log.debug("time3->{},i->{}", System.currentTimeMillis(), demo12.getI()); Thread.sleep(500); log.debug("time4->{},i->{}", System.currentTimeMillis(), demo12.getI()); } public long getI() { return i; } public void setI(long i) { this.i = i; } }
結果:
2018-12-07 [cn.qlq.thread.three.Demo12]-[DEBUG] time1->1544187679161,i->89828826
2018-12-07 [cn.qlq.thread.three.Demo12]-[DEBUG] time2->1544187679668,i->89828826
2018-12-07 [cn.qlq.thread.three.Demo12]-[DEBUG] time3->1544187680670,i->298679684
2018-12-07 [cn.qlq.thread.three.Demo12]-[DEBUG] time4->1544187681170,i->298679684
從控制台打印的時間以及結果看,線程確實被暫停了,而且也可以恢復。
2.suspend()和resume()的缺點---獨占(也就是suspend不會釋放鎖)
在使用suspend()和resume()的時候極易造成公共的同步對象的獨占,使其他線程無法訪問公共同步對象。
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo13 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo13.class); private SyncObj2 syncObj2; public Demo13(String name, SyncObj2 syncObj2) { this.setName(name); this.syncObj2 = syncObj2; } @Override public void run() { syncObj2.testSync(); } public static void main(String[] args) throws InterruptedException { SyncObj2 syncObj2 = new SyncObj2(); Demo13 demo131 = new Demo13("a", syncObj2); demo131.start(); // 休眠是為了使上面線程占用鎖 Thread.sleep(1000); Demo13 demo132 = new Demo13("b", syncObj2); demo132.start(); // 打印狀態 System.out.println("demo131.getState()->" + demo131.getState()); System.out.println("demo132.getState()->" + demo132.getState()); } } class SyncObj2 { public synchronized void testSync() { System.out.println("進入testSync同步方法"); if ("a".equals(Thread.currentThread().getName())) { System.out.println("此線程將掛起,永遠不會釋放鎖,其他線程無法調用此方法"); Thread.currentThread().suspend(); } System.out.println("退出testSync同步方法"); } }
結果:(suspend之后線程處於可運行狀態,等待鎖的處於阻塞狀態)
進入testSync同步方法
此線程將掛起,永遠不會釋放鎖,其他線程無法調用此方法
demo131.getState()->RUNNABLE
demo132.getState()->BLOCKED
jvisualvm查看線程狀態:
另外一個需要注意的常見寫法:(關於System.out.print()的同步)
package cn.qlq.thread.three; public class Demo14 extends Thread { int i; @Override public void run() { while (true) { System.out.println(i++); } } public static void main(String[] args) throws InterruptedException { Demo14 demo14 = new Demo14(); demo14.start(); Thread.sleep(100); demo14.suspend(); System.out.println("main end"); } }
結果:(到最后都不會打印main end)
原因:println方法是一個同步方法,所以線程suspend之后沒有釋放鎖,源碼如下:
public void println(String x) { synchronized (this) { print(x); newLine(); } }
3.suspend()和resume()的缺點---不同步
在使用suspend()和resume()的時候也容易出現因為線程的暫停而導致數據不同步的情況。
package cn.qlq.thread.three; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * suspend和resume導致數據不同步 * * @author QiaoLiQiang * @time 2018年12月7日下午9:32:50 */ public class Demo15 extends Thread { private static final Logger log = LoggerFactory.getLogger(Demo15.class); private SyncObj3 syncObj3; @Override public void run() { syncObj3.setVal("b", "bbb");// 啟動線程設置值 } public Demo15(String name, SyncObj3 syncObj3) { this.setName(name); this.syncObj3 = syncObj3; } public static void main(String[] args) throws InterruptedException { SyncObj3 syncObj3 = new SyncObj3(); Demo15 demo15 = new Demo15("b", syncObj3); demo15.start(); log.debug("main start"); Thread.sleep(3 * 1000); demo15.suspend();// 暫停線程 Thread.sleep(5 * 1000); log.debug("syncObj31->{}", syncObj3); demo15.resume();// 恢復線程 Thread.sleep(1 * 1000); log.debug("syncObj32->{}", syncObj3); Thread.sleep(2000); log.debug("syncObj33->{}", syncObj3); } } class SyncObj3 { private String username = "a"; private String password = "aaa"; private static final Logger log = LoggerFactory.getLogger(SyncObj3.class); public void setVal(String username, String password) { this.username = username; try { log.debug("{}線程將要休眠5秒鍾", Thread.currentThread().getName()); Thread.sleep(5 * 1000); log.debug("{}線程睡醒了", Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("{}線程設置密碼的值,password->{}", Thread.currentThread().getName(), password); this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "SyncObj3 [username=" + username + ", password=" + password + "]"; } }
結果:
21:58:51 [cn.qlq.thread.three.Demo15]-[DEBUG] main start
21:58:51 [cn.qlq.thread.three.SyncObj3]-[DEBUG] b線程將要休眠5秒鍾
21:58:59 [cn.qlq.thread.three.Demo15]-[DEBUG] syncObj31->SyncObj3 [username=b, password=aaa]
21:58:59 [cn.qlq.thread.three.SyncObj3]-[DEBUG] b線程睡醒了
21:58:59 [cn.qlq.thread.three.SyncObj3]-[DEBUG] b線程設置密碼的值,password->bbb
21:59:00 [cn.qlq.thread.three.Demo15]-[DEBUG] syncObj32->SyncObj3 [username=b, password=bbb]
21:59:02 [cn.qlq.thread.three.Demo15]-[DEBUG] syncObj33->SyncObj3 [username=b, password=bbb]
解釋:main線程和b線程線程開始之后,主線程睡了3秒鍾之后暫停了b線程,b線程此時也睡了3秒鍾(還剩余睡眠2秒鍾),暫停5秒鍾之后恢復b線程,恢復之后就馬上執行睡眠之后的代碼(也就是暫停前的代碼),所以沒有繼續睡眠之前剩余的兩秒鍾。總結起來:線程恢復之后會繼續執行暫停時的代碼,而且暫停過程中睡眠時間也在走(暫停不會導致睡眠時間的延遲)。
總結:
suspend()方法可以暫停線程,而且不會釋放同步鎖,而且暫停不會導致睡眠時間的延長;
resume()可以使線程恢復狀態,而且會繼續執行暫停前的剩余代碼。
上面兩個方法都是過期作廢的方法。
文中用到的相關知識點:引用傳遞、一個對象一把鎖、synchronized方法實則是對當前對象加鎖、synchronized同步代碼塊(關於同步會在下篇繼續學習)。