Java中的Callable与Future接口的使用

Callable比Runnable多返回结果和抛出受检异常的能力;其call()方法必须有返回值且可声明throws任何异常,而Runnable的run()方法返回void且不能抛出受检异常。

Callable比Runnable多什么能力

Callable能返回结果、抛出受检异常,而Runnable不能。这是最根本的区别——如果你的任务需要计算一个值(比如查数据库后返回ID),或者可能遇到IOException这类必须处理的异常,就该用Callable,而不是硬套Runnable

注意:Callable的泛型参数就是返回类型,比如Callable表示执行后返回String;而Runnable.call()不存在,它的方法叫run()且无返回值。

  • Callable.call() 方法必须有返回值,且可声明 throws 任何异常
  • Runnable.run() 方法返回 void,不能抛出受检异常
  • 直接 new Thread(new Runnable()) 可以启动,但 new Thread(new Callable()) 编译不通过——Callable 不能直接交给 Thread

Future.get() 为什么会阻塞,怎么避免卡死

Future.get() 是同步等待结果的操作,调用时线程会挂起,直到任务完成或超时。常见错误是没设超时,结果依赖的服务响应慢,整个线程被拖住。

推荐始终使用带超时的重载:future.get(3, TimeUnit.SECONDS)。超时后抛出TimeoutException,你可以降级、重试或记录告警,而不是让线程无限等下去。

  • 不带参数的 get() 可能永远阻塞(比如任务死循环或未提交)
  • 超时单位别写错:TimeUnit.MILLISECONDSTimeUnit.SECONDS 差1000倍
  • 如果任务已取消,get() 会抛 CancellationException,需单独捕获
try {
    String result = future.get(2, TimeUnit.SECONDS);
    System.out.println(result);
} catch (TimeoutException e) {
    System.err.println("任务超时,执行降级逻辑");
} catch (ExecutionException 

e) { Throwable cause = e.getCause(); System.err.println("任务执行异常:" + cause.getClass().getSimpleName()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 }

submit(Callable) 返回的 Future 能取消吗

可以,但是否真正生效取决于任务当前状态和实现方式。future.cancel(true)true 表示「尝试中断正在运行的线程」,但这只是个提示——线程是否响应中断,由任务自己决定。

比如你在call()里写了while (!Thread.currentThread().isInterrupted())并定期检查,那中断才有效;如果任务在做纯 CPU 计算且没检查中断,cancel 就不会停止它。

  • 任务还没开始:cancel 成功,状态变为 isCancelled() == true
  • 任务正在运行:仅当线程响应中断(如调用 Thread.sleep()BlockingQueue.take() 等可中断方法)才会退出
  • 任务已完成:cancel 失败,isCancelled() 返回 false,isDone() 返回 true

为什么常用 ExecutorService.submit(Callable) 而不是直接 new FutureTask

直接 new FutureTask 是可行的,但它要你手动管理线程(比如传给new Thread(task).start()),容易漏掉异常处理、线程复用、资源关闭等问题。而ExecutorService.submit() 内部封装了这些,还统一了生命周期管理。

更重要的是:ExecutorService 支持批量提交、优雅关闭(shutdownNow() 会尝试 cancel 所有未完成的 Future)、以及线程池复用——这对高频小任务很关键。

  • FutureTaskRunnableFuture 的组合,适合需要手动控制执行时机的场景(比如延迟触发)
  • 日常异步任务,优先走 executor.submit(new MyCallable()),别绕路
  • 别忘了在应用退出前调用 executor.shutdown()shutdownNow(),否则 JVM 可能不退出

真正容易被忽略的点是:Future 不代表任务一定在执行中——它只是一张“凭证”,背后任务可能已被拒绝、取消、或甚至还没排上队。判断状态得靠 isDone()isCancelled() 配合 get() 的异常类型,不能光看 future 对象是否存在。