启动一个线程或任务都是很简单,线程一般在任务结束后便会自行停止。但是有时我们希望能够在线程自行停止前能够停止它们,比如一些取消操作,或者是应用程序需要快速关闭。博主日前就遇到了这样的问题。
但是在《JAVA并发编程实践》一书中指出:
Java没有提供任何机制,来安全地强迫停止手头地工作。
一般来讲,对于Runnable来说,需要做的任务都是在run方法里面进行的,停止一个线程也可以认为是从run方法跳出来吧。
这种方式的基本思想为:使用一个标志位,通过这个标志位来决定run方法是继续执行还是直接结束。如下:
static class CancelledTaggedRunnnable implements Runnable{
private volatile boolean cancelled = false;
@Override
public void run() {
while(!cancelled){
//没有停止需要做什么操作
}
//线程停止后需要干什么
System.out.println("任务结束了");
}
public void cancell(){
cancelled = true;
}
}
定义一个标志位:cancelled,cancelled为true是即run方法结束,反之继续while里面的任务。通过cancell方法来停止任务。
写一个测试类:
@Test
public void testCancelledTaggedRunnnable() throws InterruptedException {
CancelledTaggedRunnnable taggedRunnnable = new CancelledTaggedRunnnable();
Thread thread = new Thread(taggedRunnnable);
thread.start();
System.err.println(thread.isAlive());
Thread.sleep(1000);
taggedRunnnable.cancell();
Thread.sleep(1000);
System.err.println(thread.isAlive());
Thread.sleep(1000);
System.err.println(thread.isAlive());
}
上述代码的操作为:启动该线程,1s秒调用cancell方法,从而来停止该线程。结果如下
但是使用标志位这种方法有个很大的局限性,那就是通过循环来使每次的操作都需要检查一下标志位。
Java还提供了中断协作机制,能够使一个线程要求另外一个线程停止当前工作。其大致的思想为:调用线程Thread的interrupt方法,在线程内部通过捕获InterruptedException异常来决定线程是否继续还是退出。如下:
static class InterruptRunnable implements Runnable{
private BlockingQueue queue = new ArrayBlockingQueue(10);
@Override
public void run() {
int i= 0;
for (;;) {
try {
//线程的操作
i++;
queue.put(i);
} catch (InterruptedException e) {
//捕获到了异常 该怎么做
System.out.println(queue);
e.printStackTrace();
return;
}
}
}
}
上述代码通过BlockingQueue的put方法来抛出InterruptedException异常。当内部捕获到该异常时,从而决定是否继续还是直接退出了。
写个测试方法:
@Test
public void testInterruptRunnable() throws InterruptedException {
InterruptRunnable runnable = new InterruptRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.err.println(thread.isAlive());
Thread.sleep(1000);
thread.interrupt();
Thread.sleep(1000);
System.err.println(thread.isAlive());
Thread.sleep(1000);
System.err.println(thread.isAlive());
}
该测试方法大致同使用标志位的测试方法,同样启动该线程后,1秒后调用线程的interrupt方法,从而触发Runnable的内部queue.put(i)操作抛出InterruptedException异常。 测试结果如下:
对于多线程,使用中断策略 较为优雅,也是官方的推荐。线程停止前你应该做一些操作,从而保证该线程运行后的数据不会被丢失,这一点在多线程中极为重要。
但是对于中断策略还是有一个很大的缺陷,那就是,必须通过中断的阻塞函数,如:我使用的BlockingQueue的put方法,也可以是Thread.sleep()方法,才能抛出InterruptedException。如果抛不出这样的异常呢?
对于单线程,还有一个简单粗暴的方式,那就是Java已经不再使用的Thread stop方法。
使用方法即直接调用:thread.stop()即可。
如果你对该线程的操作不再关心了,对结果也不再在意了,使用该方法也是可以的。
但多线程情况下,或者线程带锁的情况下 那就要慎用了。该方法不安全
Java还提供一个可带返回值的线程操作,FutureTask。在这个类中可以调用其cancel方法来取消这个任务。
你可以设置true或者false来决定是否让线程出现中断的操作。从而可以使这个中断后能够捕获InterruptedException来决定是否让操作结束。
一般来说,这个类都是跟带返回值的Callable接口一起使用,Runnable接口也是可以使用的,定义线程的操作:
static class MyRunnable implements Runnable{ @Override public void run(){ for(;;){ try { Thread.sleep(300); } catch (InterruptedException e) { System.out.println("线程被中断"); return; } System.out.println("程序运行中...."); } } }
上述代码较简单,不需要多说了。使用FutureTask进行测试,先不让产生中断:
@Test public void test01() throws Exception { FutureTask task = new FutureTask<>(new MyRunnable(), 0); ExecutorService pool = Executors.newFixedThreadPool(1); pool.submit(task); Thread.sleep(1000); System.out.println("任务是否完成?"+task.isDone()); System.out.println("任务是否被取消?"+task.isCancelled()); Thread.sleep(1000); //在这里取消操作 System.out.println(task.cancel(true)); Thread.sleep(1000); System.out.println("任务是否完成?"+task.isDone()); System.out.println("任务是否被取消?"+task.isCancelled()); Thread.sleep(5000); }
通过线程池的方式来提交任务,让其运行,2秒后取消任务,查看控制台:
但是FutureTask的cancelle方法并不能真正的停止当前线程,其主要思想还是如同中断策略。也就是说,如果我们不设置task.cancell(true),那么任务还不会被中断,如,我们改为task.cancell(false),结果如下:
虽然此时调用了task.cancell(false)方法,但是通过isDone,isCancelled方法并不能说名任务就结束了。如上,任务还在继续。
综上,Java并没有停止线程的方法,如果需要手动的停止,还是建议较优雅的中断策略,或者FutureTask。