java中thread执行任务取消

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,表示阻塞操作由于中断而提前结束。对中断操作的正确理解是:它不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值