- 参考连接-01-线程中断以及线程中断引发的那些问题;
- 参考连接-02-Java实现终止线程池中正在运行的定时任务;
1),停止?or 取消?
在使用线程池进行异步任务执行时,通常情况下不会直接停止线程池中正在运行的线程。相反,我们可以采用一种更加协调和优雅的方式来处理这个需求,即通过取消任务的方式来停止线程池中的某个正在运行的线程。
-
停止(中断)线程:我们知道可以使用
interrupt
方法来停止一个线程,其也有自己的局限性,由于中断线程的底层方法是将interrupted()
的属性值设置为ture
。- (1),所以对于非阻塞的线程,只是改变了中断状态为
ture
,具体的逻辑还需要被中断的的线程自己处理。即如果线程自己不检查中断状态或者不进行相应的处理,那么interrupt
方法本身并不会立即终止线程的执行。 - (2),而对于阻塞中的线程,在接收到的中断信号之后会抛出异常(
InterruptedException
),并将中断状态设置为ture
。- 这里说的阻塞状态包含:
sleep()
、wait()
等,其都会立即抛出异常并清除中断状态。 - 其他阻塞状态,如IO操作、
Lock
、BlockingQueue
等,也会抛出异常并结束线程的阻塞状态。 - 通过分析其源码也可以得出相同的结论,如下是对应的伪代码:
public static void sleep(long millis) throws InterruptedException { // 检查中断状态 if (Thread.interrupted()) { // 抛出InterruptedException异常并清除中断状态 throw new InterruptedException(); } // 调用底层平台的休眠方法 // ... }
- 这里说的阻塞状态包含:
- (3),注意,在捕获到
InterruptedException
异常后,线程可以根据自身的逻辑进行相应的处理,例如恢复中断状态、执行清理操作、终止线程等。- 1),
interrupted()
方法却能改变interrupted
的属性值; - 2),
isInterrupted()
方法不能改变interrupted
的属性值,可以用来判断线程是否被中断了,从而可以通过这个判断进行额外的逻辑处理。
- 1),
- (1),所以对于非阻塞的线程,只是改变了中断状态为
取消线程:这个概念是出现在线程池中的,即如果想中断线程池中的一个线程可以使用
t.cancel(true)
去实现,在其方法内部中通过t.interrupt();
便可以实现线程中断。所以值的注意的是无论是哪种方式都会归结到interrupt
方法,而这个方法本身是无法中断正在运行的线程的,而是要配合一个额外的逻辑处理。
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
@Component
public class ThreadPoolManager {
private ThreadPoolTaskExecutor taskExecutor;
public ThreadPoolManager() {
taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.initialize();
}
public void executeTask(String taskId) {
taskExecutor.submit(() -> {
// 异步任务的逻辑
// 可能是一个长时间运行的任务
// 判断任务是否被取消
if (Thread.currentThread().isInterrupted()) {
System.out.println("Task " + taskId + " is canceled.");
return;
}
// 执行任务逻辑
System.out.println("Executing task " + taskId);
try {
Thread.sleep(5000); // 模拟耗时操作
} catch (InterruptedException e) {
System.out.println("Task " + taskId + " is interrupted.");
return;
}
System.out.println("Task " + taskId + " completed.");
});
}
public void cancelTask(String taskId) {
// 模拟取消任务
for (Runnable runnable : taskExecutor.getThreadPoolExecutor().getQueue()) {
if (runnable instanceof ThreadPoolTask) {
ThreadPoolTask threadPoolTask = (ThreadPoolTask) runnable;
if (threadPoolTask.getTaskId().equals(taskId)) {
threadPoolTask.cancel();
System.out.println("Task " + taskId + " is requested to cancel.");
break;
}
}
}
}
}
2),@Async注解与CompletableFuture
结论:在Spring框架中,无法直接取消或移除线程池中正在执行的特定任务。@Async
注解将方法提交给线程池后,无法直接操作线程池中的具体任务。
- 我们可以增加一个判断标志变量并定期检查该变量的值。当标志变量为指定的取消状态时,提前退出循环以取消任务。示例代码如下所示:
@Async("asyncExecutor")
public void asyncUpdateUserName(List<UserVo> userVoList) {
log.info("异步更新用户名");
// 取消标志变量
boolean cancelled = false;
for (UserVo userVo : userVoList) {
if (cancelled) {
log.info("任务被取消,提前退出循环");
break;
}
userService.usedateUserName(userVo.getId(), userVo.getName());
}
}
// 取消任务的示例代码:根据合适的条件下设置变量的结果值
// 设置取消标志变量为true
boolean cancelled = true;
- 使用
Future
对象:修改asyncUpdatePrintTopology
方法的返回类型为Future<?>
,并在调用该方法时获取返回的Future
对象。然后,在需要取消任务的时候,调用Future
对象的cancel()
方法进行取消操作。示例代码如下所示:
@Async("asyncExecutor")
public Future<?> asyncUpdateUserName(List<UserVo> userVoList) {
log.info("异步更新用户名");
for (UserVo userVo : userVoList) {
userService.usedateUserName(userVo.getId(), userVo.getName());
}
return new AsyncResult<>(null);
}
// 取消任务的示例代码
Future<?> future = asyncUpdateUserName(userVoList);
future.cancel(true); // true表示是否允许正在运行的任务中断
使用Future
对象的优点是可以实现较为简单的任务取消操作,但无法强制终止正在执行的任务。