一般来说,线程在执行完毕后就会结束,无须手动关闭。但是,凡是也有例外,一些服务端的后台程序可能会常驻系统,它们通常不会正常终结。比如,他们的执行体本身就是一个大的无穷循环。
stop()
在Thread中有一个stop()方法,如果使用stop()方法可以立即将一个线程中止,但是这个方法已经被废弃。因为这个方法是在是太过暴力,会强行将执行一般的线程直接终止,并且会立即释放这个线程所持有的锁,这样就会引发很多问题。
下面简单实现一个安全的停止线程的方式:
//......
public class StopThreadTest extends Thread {
volatile boolean stopMe = false;
public void stopMe(){
stopMe = true;
}
@Override
public void run() {
while (true){
if (stopMe){
System.out.println("线程【"+this.getName()+"】stop......");
break;
}
//do sth
System.out.println("正在执行!!!");
Thread.yield();
}
}
}
//......
suspend()和resume()
suspend()(挂起)和resume()(继续执行)是一对相反的操作,被挂起的线程必须要等到resume()后才能够继续执行。看着好像很好用,但是这两个方法也已经被废弃了。不推荐使用suspend()的原因是因为suspend()在导致线程暂停的同时,并不会释放任何资源,这样就会有个问题,如果当前线程A持有一把锁,当A被挂起之后,其他任何线程想要访问被线程A占用的锁时,都会被牵连,导致其他线程无法正常地运行,除非线程A执行了resume()方法,而一般线程A的resume()方法肯定是由另一个线程执行的,而线程的很多操作是难以控制的,如果resume()方法意外地在suspend()方法前面执行了,那么线程A就难以继续执行,而它持有的锁也不会释放这样就会引发死锁问题。
而且被挂起的线程是处于RUNNABLE(具体可以参看:并发编程学习之线程的生命周期)状态,这样会验证影响我们对系统当前状态的判断。
以下代码示例简单演示了使用suspend()会出现的问题:
package dgb.test.concurrent;
/**
* @author Dongguabai
* @date 2018/9/2 19:35
*/
public class SuspendTest {
public static void main(String[] args) throws InterruptedException {
ChangeObjectThread c1 = new ChangeObjectThread("thread-1");
ChangeObjectThread c2 = new ChangeObjectThread("thread-2");
c1.start();
c2.start();
// Thread.sleep(1000);
c1.resume();
c2.resume();
}
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("线程【" + getName() + "】正在执行");
Thread.currentThread().suspend();
}
}
}
测试结果:
由于resume()意外的在suspend()前面执行了。两个线程不会退出,而是会挂起。可以查看线程信息看看(具体查看方法可以参看:jstack使用):
这时候发现线程的状态是RUNNABLE的,但是实际上它是被挂起的,这样会使我们误判当前线程的状态。
这里有一个示例使用wait()和notify()实现suspend()和resume(),思路和上面是一样的:
package dgb.test.concurrent;
import java.time.LocalDateTime;
/**
* @author Dongguabai
* @date 2018/9/2 19:35
*/
public class SuspendTest2 {
private static final Object u = new Object();
public static void main(String[] args) throws InterruptedException {
ChangeObjectThread c1 = new ChangeObjectThread("thread-1");
ReadThread r = new ReadThread();
c1.start();
r.start();
Thread.sleep(1000);
c1.mySuspend();
Thread.sleep(60000);
c1.myResume();
}
public static class ChangeObjectThread extends Thread {
private volatile boolean mySuspend = false;
public ChangeObjectThread(String name) {
super(name);
}
public void mySuspend() {
// System.out.println("线程【" + getName() + "】mySuspend==");
mySuspend = true;
}
public void myResume() {
//System.out.println("线程【" + getName() + "】myResume==");
mySuspend = false;
synchronized (this) {
this.notify();
//System.out.println("线程【" + getName() + "】执行notify==");
}
}
@Override
public void run() {
while (true) {
synchronized (this) {
while (mySuspend) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized (u) {
System.out.println("线程【" + getName() + "】正在执行,当前时间:" + LocalDateTime.now());
}
Thread.yield();
}
}
}
public static class ReadThread extends Thread {
@Override
public void run() {
while (true) {
synchronized (u) {
System.out.println("ReadThread执行。。。。");
}
Thread.yield();
}
}
}
}
线程中断
线程的中断时一种重要的线程协作机制,线程中断并不会使线程立即退出,而是给线程发送一个通知,告诉线程,有人想你退出了,至于目标线程后续会如何处理,则是由目标线程自己决定,是与stop()不同的。
关于线程中断有三个方法:
public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
Thread.interrupt()方法是一个实例方法。它通知目标线程中断,也就是设置中断标志位。中断标志位表示当前线程已经被中断了。Thread.isInterrupted() 也是一个实例方法,它判断当前线程是否有被中断(检查标志位)。最后的静态方法Thread.interrupted()也是用来判断当前线程的字段状态,但同时会清除当前线程中断标志位状态。
在下面这个示例中,虽然设置了中断标识位,但是线程中并没有中断处理的逻辑,因此,即使线程被设置了中断标志位,但是这个中断不会起任何作用:
如果想让线程在中断后退出,就要增加相应的中断处理代码,这样就不会武断的将线程中止,这样的操作更加安全和优雅:
package dgb.test.concurrent;
/**
* @author Dongguabai
* @date 2018/9/2 23:13
*/
public class InterruptedTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
if(Thread.currentThread().isInterrupted()){
System.out.println("线程已被中断-------");
break;
}
System.out.println("正在执行------------");
}
}
);
thread.start();
Thread.sleep(1000);
System.out.println("中断。。。。。");
thread.interrupt();
}
}
在Java的API中,很多声明都会抛出InterruptedException异常,这些方法在抛出InterruptedException之前。JVM会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。
InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它。
比如修改一下上面那段代码:
如果在第20行处,线程被中断了,那么程序会进入23行,此时中断标识位已被清除,在这里又执行了一次Thread.interrupt()方法中断自己,只有这样,当再次执行的时候会发现线程已经中断了,从而能够保证数据的一致性和完整性。