通常如下情况会取消:
1. 用户发起取消请求
2. 现实的活动
3. 分解任务其中一条发现了解决方案,其他的就可以取消了
4. 分解任务其中一条发现了对于其他任务都有影响的错误,比如磁盘空间已满,其他的可以取消了
5. 关闭, 当执行器关闭的时候,必须对正在处理及等待处理的任务进行优雅的关闭。
一个最简单的方式是,加上取消标记,cancel方法设置取消标记。 主流程中判断取消标记,进行操作
public class TestCallable {
public static void main(String[] args) {
ThreadTest thread = new ThreadTest();
thread.start();
thread.cancel();
}
}
class ThreadTest extends Thread{
private boolean cancel = false;
@Override
public void run() {
while(!cancel){
doSomething();
}
System.out.println("被取消了");
}
private void doSomething() {
// ...
}
public void cancel(){
this.cancel = true;
}
}
这样有个问题,如果doSomthing()的时候有阻塞,就永远不会监测到cancel的状态, 在JDK中我们还能使用专门为了取消而存在的中断方法。
上面的代码就会改为这样:
public class TestCallable {
public static void main(String[] args) {
ThreadTest thread = new ThreadTest();
thread.start();
thread.cancel();
}
}
class ThreadTest extends Thread {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("被取消了");
}
private void doSomething() throws InterruptedException {
// ...
TimeUnit.SECONDS.sleep(1000);
}
public void cancel() {
interrupt();
}
}
这里再来看一个用法,使用超时加上中断来决定任务执行多久:
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
final ThreadTest thread = new ThreadTest();
service.schedule(new Runnable() {
@Override
public void run() {
thread.cancel();
}
}, 1, TimeUnit.SECONDS);
thread.start();
}
中断策略
给线程加中断有一个原则就是一定要清楚中断策略,否则就不要使用中断方法。
使用Future完成取消
例如:
public class TestCallable {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(1);
ThreadTest thread = new ThreadTest();
Future f = service.submit(thread);
Future f2 = service.submit(new ThreadTest());
try {
f.get(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
f.cancel(true);
f2.cancel(false);
}
}
}
class ThreadTest extends Thread {
@Override
public void run() {
System.out.println("开始运行1");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完成1");
}
}其中断策略为:
.cancel(true),表示如果运行尝试对当前线程进行中断,一般是调用当前线程的.interrupt()方法
.cancel(false),表示如果没运行则不会运行这个线程了,如果运行了,会等到运行完成。
停止基于线程的服务
比如生产者消费者模式。
因为其使用了take()阻塞的方法,能够响应中断,所以如果生产者阻塞了不是问题,但是这样中断可能不太好。
我们可以加入状态标志,当设置了关闭标志之后,再生产就会抛出异常, 这个跟Executor的shutdown方法是一样的。
还有一种方式是使用致命药丸,就是在队列中加入一个特殊的任务,执行到这个药丸就停止服务。
使用TrackingExecutor类还能够获取已经取消了的任务, 用exec.getCancelledTasks()来获取
任务中的异常处理
在Executors.newFixedThreadPool(int , ThreadFactory threadFactory)中的第二个构造参数,是当一个线程异常中断的时候从这个factory中创建新的线程补充进去,可以用来做异常处理
或者一般情况下,使用Future.get方法可以得到异常
JVM关闭
正常关闭,System.exit()可以注册关闭钩子如下:
public class TestCallable {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
System.out.println("运行于JVM退出之前");
}
});
System.exit(0);
}
}
daemon的线程,不会影响线程的退出。不会执行finally块,通常用于内部的事务处理。
避免使用Finalizer.不提供任何保证,并且会带来巨大的性能开销。