线程的停止与暂停
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同步代码块(关于同步会在下篇继续学习)。