Futrue.cancel()举例
在执行一些耗时较长的逻辑时,经常会通过创建新线程异步处理来减少程序响应时间,但一些特殊场景下由于种种原因,用户希望能够取消本次提交的任务。这里使用thread的interrupt方法配合一些具体业务代码,来实现取消任务的功能。
先看如下代码,通过ExecutorService和Callable处理任务提交
@Slf4j
@Service
public class ThreadEndService implements IThreadEndService {
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
private final Map<String, Future> executeMap = new ConcurrentHashMap<>();
@Override
public String doSomethingWithSleep() {
final String key = UuidTools.uuid();
//定义任务
Callable<String> task = () -> {
String title = "Andy";
for(int i=0; i<30; ++i){
log.info("title is running at {} times", i);
Thread.sleep(2);
}
//执行完毕
executeMap.remove(key);
return title;
};
Future<String> futrue = executorService.submit(task);
executeMap.put(key, futrue);
return key;
}
@Override
public boolean tryInterrupt(String key) {
Future curr = executeMap.get(key);
if(curr == null){
log.warn("找不到正在执行的任务 {}", key);
return false;
}
//中断任务
boolean interrupted = curr.cancel(Boolean.TRUE);
if(interrupted){
executeMap.remove(key);
}
return interrupted;
}
}
在controller层通过如下代码新建线程并尝试取消任务
public String startThreads() {
String key = threadEndService.doSomethingWithSleep();
try {
Thread.sleep(20);
threadEndService.tryInterrupt(key);
} catch (InterruptedException e) {
log.error("sleep not well", e);
}
return key;
}
通过日志发现doSomethingWithSleep方法,按预期只输出了10次左右的"title is running at {} times"信息。那么是不是在取消任务时只用Future的cancel就可以了呢?答案是否。Future的cancel方法在FutureTask的实现中是调用Thread.interrupt()方法的,而Thread.interrupt()只会标记目标线程的状态,而不会真正的去中断线程。
将threadEndService.doSomethingWithSleep()替换成如下的threadEndService.doSomething()(使用FileWiter只是想通过io操作增加任务的执行时间)
@Override
public String doSomething() {
final String key = UuidTools.uuid();
//定义任务
Callable<String> task = () -> {
String title = "Coke";
FileWriter fw = null;
try{
long start = System.currentTimeMillis();
File file = new File("C:\\Users\\haha\\Desktop\\test\\111.txt");
fw = new FileWriter(file);
for(int i=0; i<1000; ++i){
/*if(Thread.currentThread().isInterrupted()){
break;
}*/
log.info("title is running at {} times", i);
fw.write(String.format("title is running at %d times\r\n", i));
}
fw.flush();
long end = System.currentTimeMillis();
log.info("write title lines take {} milliseconds", (end - start));
}finally {
if(fw!=null){
fw.close();
}
}
//执行完毕
executeMap.remove(key);
return title;
};
Future<String> futrue = executorService.submit(task);
executeMap.put(key, futrue);
return key;
}
再次通过controller层调用,发现日志全额的输出了1000次的"title is running at {} times"信息。这里也印证了Thread.interrupt()不会中断线程,但会影响sleep,wait等方法的逻辑。所以取消线程需要针对具体业务场景具体处理。譬如这里的示例,可以删掉如下代码if(Thread.currentThread().isInterrupted()){ break;}周围的注释,让call方法提前结束,从而中断执行任务。
Async注解举例
通常在spring项目中会使用@Async注解来实现异步处理,可以按如下实现来达到取消任务效果
@Slf4j
@Service
public class ThreadAsynService implements IThreadAsynService {
private Map<String, Thread> executeMap = new ConcurrentHashMap<>();
@Async
@Override
public void doSomething(String key) {
FileWriter fw = null;
try{
executeMap.put(key, Thread.currentThread());
long start = System.currentTimeMillis();
File file = new File("C:\\Users\\haha\\Desktop\\test\\111.txt");
fw = new FileWriter(file);
for(int i=0; i<1000; ++i){
if(Thread.currentThread().isInterrupted()){
break;
}
log.info("async is running at {} times", i);
fw.write(String.format("async is running at %d times\r\n", i));
}
fw.flush();
long end = System.currentTimeMillis();
log.info("write title lines take {} milliseconds", (end - start));
} catch (IOException e) {
e.printStackTrace();
} finally {
executeMap.remove(key);
try {
if(fw!=null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public boolean tryInterrupt(String key) {
Thread task = executeMap.get(key);
if(task == null){
log.warn("cannot find the thread named {}", key);
return Boolean.FALSE;
}
task.interrupt();
return task.isInterrupted();
}
}
这里的if(Thread.currentThread().isInterrupted()){ break;}只是举例,其实只要使具体任务代码块体提前结束即可,或return或抛出异常。有些情况需要注意已经执行任务的数据回滚。
说明
每个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true。阻塞库方法,例如Thread.sleep()和Object.wait()等,都会检查线程何时中断,并在发现中断时提前返回。它们在响应中断时执行的操作包括:清除中断状态,抛出InterruptedException,表示阻塞操作由于中断而提前结束。对中断操作的正确理解是:它不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。