futuretask.get()是阻塞式获取异步任务结果的方法,基于locksupport.park()/unpark()挂起唤醒机制;需配合超时、异常处理以保障主线程安全,适用于一次性、显式控制的任务场景。
futuretask.get() 是 java 并发编程中一个天然的“等待锚点”——它会让调用线程(比如主线程)暂停执行,直到异步任务完成并返回结果。这种阻塞不是轮询、不是忙等,而是基于底层 locksupport.park() 的挂起机制,既省资源又可靠。
一、get() 阻塞的本质:线程挂起 + 结果就绪唤醒
当调用 future.get() 时:
- 如果任务已完成(
state == NORMAL),直接返回结果,不阻塞; - 如果任务未完成(
state ),当前线程会被加入等待队列,并调用 <code>LockSupport.park()挂起; - 当任务执行结束(无论成功或异常),会遍历等待队列,对每个线程调用
LockSupport.unpark()唤醒; - 被唤醒的线程重新检查状态,取出结果或抛出异常。
二、在主线程中安全等待的典型写法
不要裸调 get(),要处理超时和异常:
- 用
get(long timeout, TimeUnit unit)避免无限等待,防止主线程永久卡死; - 捕获
ExecutionException(包装了任务内部抛出的异常); - 捕获
InterruptedException(主线程被中断时抛出); - 捕获
TimeoutException(超时未完成)。
示例:
FutureTask<string> task = new FutureTask(() -> {
Thread.sleep(2000);
return "done";
});
new Thread(task).start();
try {
String result = task.get(3, TimeUnit.SECONDS); // 主线程在此阻塞最多3秒
System.out.println(result); // 输出 "done"
} catch (TimeoutException e) {
System.err.println("任务超时,可能需要降级或重试");
} catch (ExecutionException e) {
System.err.println("任务执行出错:" + e.getCause());
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
System.err.println("主线程被中断");
}
</string>
三、与 CompletableFuture 对比:何时选 FutureTask?
FutureTask 更适合简单、一次性、需显式控制生命周期的场景:
- 你明确要复用同一个
Runnable/Callable,且希望多次提交但只执行一次(FutureTask天然支持“runOnce”); - 你需要把任务对象本身作为参数传递(比如放进队列、做状态管理),而不仅是一个
Future接口; - 项目受限于老 JDK(如 Java 6/7),无法使用
CompletableFuture; - 你偏好命令式等待风格,而非函数式链式回调(
thenApply等)。
注意:FutureTask 不支持组合、无默认线程池、不能链式编排——这些是 CompletableFuture 的优势。
四、容易踩的坑
- 重复调用 get() 不会重复执行任务:FutureTask 是“惰性求值+结果缓存”,多次 get 返回同一结果(或异常);
- 构造后必须 start() 或 submit():仅 new 出来不会自动运行,必须交给线程执行;
-
不要在 get() 前调用 cancel(true):若已 cancel,get() 会立即抛
CancellationException; - 避免在持有锁时调用 get():可能导致死锁(例如 synchronized 块内等待,而任务内部又试图获取同一把锁)。
不复杂但容易忽略:阻塞是手段,不是目的;关键是在等待期间让主线程“可响应、可中断、有退路”。











