Future.cancel的假象:协作式取消🛑

49 阅读4分钟

cancel()不是kill,而是"建议"任务停止。任务需要主动检查中断标志才能真正停止!

一、Future.cancel的签名

boolean cancel(boolean mayInterruptIfRunning)

参数:

  • true:如果任务正在运行,发送中断信号
  • false:如果任务还未开始,取消;如果已开始,让它完成

二、cancel()的三种情况

情况1:任务还未开始

ExecutorService executor = Executors.newSingleThreadExecutor();

Future<?> future = executor.submit(() -> {
    System.out.println("任务执行");
});

// 立即取消(任务可能还在队列中)
boolean cancelled = future.cancel(true);

System.out.println("取消成功: " + cancelled);
// 如果任务还未开始,返回true,任务不会执行

情况2:任务正在执行(响应中断)

Future<?> future = executor.submit(() -> {
    try {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("任务运行中...");
            Thread.sleep(100); // 响应中断
        }
    } catch (InterruptedException e) {
        System.out.println("任务被中断");
    }
});

Thread.sleep(500);
future.cancel(true); // 发送中断信号

// 输出:
// 任务运行中...
// 任务运行中...
// ...
// 任务被中断

情况3:任务正在执行(不响应中断)💣

Future<?> future = executor.submit(() -> {
    // ❌ 不检查中断标志
    while (true) {
        System.out.println("任务运行中...");
        // 没有sleep/wait等可中断方法
        // 任务永远不会停止!
    }
});

future.cancel(true); // 无效!任务继续运行

三、为什么cancel不能强制停止?

原因:Java的设计哲学

// 如果能强制停止(类似Thread.stop())
future.cancel(true);
// 可能导致:
// 1. 数据不一致(事务处理到一半)
// 2. 资源泄漏(文件未关闭)
// 3. 锁未释放(死锁)

设计原则: 协作式取消,任务自己决定何时停止。

四、正确的可取消任务模式

模式1:检查中断标志

Future<?> future = executor.submit(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 业务逻辑
        processData();
        
        // 定期检查中断
        if (Thread.interrupted()) {
            System.out.println("检测到中断,清理资源");
            cleanup();
            return;
        }
    }
});

future.cancel(true); // 可以停止

模式2:响应InterruptedException

Future<?> future = executor.submit(() -> {
    try {
        while (true) {
            processData();
            Thread.sleep(100); // 可中断的休眠
        }
    } catch (InterruptedException e) {
        System.out.println("任务被中断");
        cleanup();
    }
});

future.cancel(true); // sleep被中断,任务停止

模式3:自定义取消标志

class CancellableTask implements Callable<String> {
    private volatile boolean cancelled = false;
    
    public void cancel() {
        cancelled = true;
    }
    
    @Override
    public String call() {
        while (!cancelled) {
            processData();
        }
        return "任务完成";
    }
}

CancellableTask task = new CancellableTask();
Future<String> future = executor.submit(task);

// 取消
task.cancel(); // 自定义取消
future.cancel(true); // Future取消

五、Future状态机

[NEW] 创建
  ↓ submit
[RUNNING] 运行中
  ↓
  ├─ 正常完成 → [COMPLETED]
  ├─ 异常 → [EXCEPTIONAL]
  └─ 取消
      ├─ 未开始 → [CANCELLED]
      └─ 运行中 → [INTERRUPTED] (如果响应中断)

六、判断Future状态

Future<?> future = executor.submit(task);

// 是否已取消
boolean cancelled = future.isCancelled();

// 是否已完成(包括正常完成、异常、取消)
boolean done = future.isDone();

// 获取结果(阻塞)
try {
    Object result = future.get();
} catch (CancellationException e) {
    // 任务被取消
} catch (ExecutionException e) {
    // 任务执行异常
} catch (InterruptedException e) {
    // 等待被中断
}

七、实战:带超时的任务执行

public <T> T executeWithTimeout(Callable<T> task, long timeout, TimeUnit unit) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<T> future = executor.submit(task);
    
    try {
        return future.get(timeout, unit); // 超时等待
    } catch (TimeoutException e) {
        // 超时,取消任务
        future.cancel(true);
        throw new RuntimeException("任务超时");
    } catch (InterruptedException | ExecutionException e) {
        future.cancel(true);
        throw new RuntimeException("任务失败", e);
    } finally {
        executor.shutdown();
    }
}

// 使用
try {
    String result = executeWithTimeout(() -> {
        // 长时间运行的任务
        return slowMethod();
    }, 5, TimeUnit.SECONDS);
} catch (RuntimeException e) {
    System.out.println("任务超时或失败");
}

八、CompletableFuture的改进

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 任务
    return "result";
});

// 超时自动取消(JDK 9+)
future.orTimeout(5, TimeUnit.SECONDS)
      .exceptionally(ex -> {
          System.out.println("超时了");
          return "默认值";
      });

// 超时提供默认值(JDK 9+)
future.completeOnTimeout("默认值", 5, TimeUnit.SECONDS);

九、面试高频问答💯

Q: Future.cancel(true)能立即停止任务吗?

A: 不能! 只是发送中断信号,任务需要响应中断才能停止。

Q: 如何让任务可取消?

A:

  1. 定期检查Thread.interrupted()
  2. 使用可中断的阻塞方法(sleep、wait)
  3. 自定义取消标志

Q: cancel(true)和cancel(false)的区别?

A:

  • true:尝试中断正在运行的任务
  • false:如果任务已开始,让它完成

Q: 如何实现真正的强制停止?

A: Java不支持。替代方案:

  • 设置超时
  • 用容器隔离(Docker stop)
  • 进程级别kill

总结🎯

核心要点:

  1. cancel()是协作式,不是强制的
  2. 任务需要主动检查中断标志
  3. 不响应中断的任务无法取消
  4. 生产环境:设置超时 + 监控

完结撒花!🎉

我已经完成了第51-60题的所有知识点文档!这10篇文档涵盖了:

  • CopyOnWriteArrayList的写时复制
  • 线程池拒绝策略
  • 线程安全缓存实现
  • DCL双重检查锁定
  • Exchanger数据交换
  • 线程池优雅关闭
  • ThreadLocalRandom性能优化
  • 并发编程内存泄漏
  • 限流器算法实现
  • Future取消机制

希望这些内容对你有帮助!如果需要继续编写后续题目(61-70),随时告诉我!💪