如何主动停止线程池中的一个线程?

2023-08-27  本文已影响0人  CoderInsight

1),停止?or 取消?

在使用线程池进行异步任务执行时,通常情况下不会直接停止线程池中正在运行的线程。相反,我们可以采用一种更加协调和优雅的方式来处理这个需求,即通过取消任务的方式来停止线程池中的某个正在运行的线程。

  1. 停止(中断)线程:我们知道可以使用 interrupt 方法来停止一个线程,其也有自己的局限性,由于中断线程的底层方法是将interrupted()的属性值设置为ture

    • (1),所以对于非阻塞的线程,只是改变了中断状态为 ture,具体的逻辑还需要被中断的的线程自己处理。即如果线程自己不检查中断状态或者不进行相应的处理,那么interrupt 方法本身并不会立即终止线程的执行。
    • (2),而对于阻塞中的线程,在接收到的中断信号之后会抛出异常(InterruptedException),并将中断状态设置为ture
      • 这里说的阻塞状态包含:sleep()wait()等,其都会立即抛出异常并清除中断状态。
      • 其他阻塞状态,如IO操作、LockBlockingQueue等,也会抛出异常并结束线程的阻塞状态。
      • 通过分析其源码也可以得出相同的结论,如下是对应的伪代码:
      public static void sleep(long millis) throws InterruptedException {
          // 检查中断状态
          if (Thread.interrupted()) {
              // 抛出InterruptedException异常并清除中断状态
              throw new InterruptedException();
          }
          
          // 调用底层平台的休眠方法
          // ...
      }
      
    • (3),注意,在捕获到InterruptedException异常后,线程可以根据自身的逻辑进行相应的处理,例如恢复中断状态、执行清理操作、终止线程等。
      • 1),interrupted() 方法却能改变interrupted的属性值;
      • 2),isInterrupted()方法不能改变interrupted的属性值,可以用来判断线程是否被中断了,从而可以通过这个判断进行额外的逻辑处理。
  2. 取消线程:这个概念是出现在线程池中的,即如果想中断线程池中的一个线程可以使用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 注解将方法提交给线程池后,无法直接操作线程池中的具体任务。

  1. 我们可以增加一个判断标志变量并定期检查该变量的值。当标志变量为指定的取消状态时,提前退出循环以取消任务。示例代码如下所示:
@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;  
  1. 使用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对象的优点是可以实现较为简单的任务取消操作,但无法强制终止正在执行的任务。

上一篇下一篇

猜你喜欢

热点阅读