线程基础与执行器
学习目标
- 区分 Thread、Runnable、Callable、Future 与线程生命周期。
- 会用 ExecutorService 管理任务,并设置等待、取消、异常和关闭策略。
- 知道平台线程、线程池和虚拟线程的适用边界。
核心知识点
专业术语
- Thread:JVM 中执行代码的线程对象,承载任务的运行上下文。
- Runnable:没有返回值、不能直接声明受检异常的任务接口。
- Callable:可以返回结果、抛出异常的任务接口。
- Executor/ExecutorService:把“提交任务”和“创建、调度线程”分离的执行器抽象。
- Future:异步任务的句柄,可以等待结果、取消任务或查询完成状态。
白话解释与边界
线程是执行资源,Runnable/Callable 是要执行的工作,ExecutorService 是管理执行资源的容器。小程序可以直接 new Thread;业务代码通常提交给有界线程池或虚拟线程执行器,避免每个请求都自行创建平台线程。线程池并不会让任务天然安全,任务共享数据时仍需要同步。
线程状态包括 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 和 TERMINATED。sleep 只让当前线程暂时不运行,不释放已经持有的锁;wait 会释放对应监视器并等待通知。中断是协作式取消信号,不是强制杀线程。
常用用法
启动平台线程:把任务交给 Thread
start() 才会创建并调度新线程,直接调用 run() 只是在当前线程执行方法。
// 说明:启动平台线程:把任务交给 Thread。
public class ThreadRunnableDemo {
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
// 初始状态:task = () ->。
System.out.println("worker=" + Thread.currentThread().getName());
// 输出:worker=worker-1
};
Thread thread = new Thread(task, "worker-1");
// 初始状态:thread = new Thread(task, "worker-1")。
// 作用:Thread thread = new Thread(task, "worker-1");;把任务交给 Thread。
thread.start();
// 初始状态:thread.start()。
// 作用:thread.start();;把任务交给 Thread。
thread.join();
// 初始状态:thread.join()。
// 作用:thread.join();;把任务交给 Thread。
System.out.println("finished=" + thread.getState());
// 输出:finished=TERMINATED
}
}需要等待线程结束时调用 join(),否则主线程可能先结束。
Thread.join:等待线程完成
join() 建立了“线程结束先于后续读取”的 happens-before 关系。
// 说明:Thread.join:等待线程完成。
public class ThreadJoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
// 作用:Thread thread = new Thread(() -> {;等待线程完成。
// 初始状态:thread = new Thread(() ->。
// 这里模拟一小段确定的工作
});
thread.start();
// 作用:thread.start();;等待线程完成。
thread.join();
// 初始状态:thread.join()。
// 作用:thread.join();;等待线程完成。
System.out.println("worker completed");
// 输出:worker completed
}
}join(timeout) 只等待一段时间,返回时线程可能仍在运行,不能把超时当成完成。
提交有结果任务:用 Future 观察完成状态
submit 返回 Future;Runnable 的结果通常是 null,Callable 可以返回值。
// 说明:提交有结果任务:用 Future 观察完成状态。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorSubmitDemo {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
// 初始状态:executor = Executors.newFixedThreadPool(2)。
// 作用:ExecutorService executor = Executors.newFixedThreadPool(2);;用 Future 观察完成状态。
try {
Future<?> done = executor.submit(() -> {
// 初始状态:done = executor.submit(() ->。
// 作用:Future<?> done = executor.submit(() -> {;用 Future 观察完成状态。
System.out.println("task=ok");
// 输出:task=ok
});
done.get();
// 初始状态:done.get()。
// 作用:done.get();;用 Future 观察完成状态。
Future<Integer> result = executor.submit(() -> 20 + 22);
// 初始状态:result = executor.submit(() -> 20 + 22)。
// 作用:Future<Integer> result = executor.submit(() -> 20 + 22);;用 Future 观察完成状态。
System.out.println("answer=" + result.get());
// 输出:answer=42
} finally {
executor.shutdown();
// 初始状态:executor.shutdown()。
// 作用:executor.shutdown();;用 Future 观察完成状态。
}
}
}get() 会等待并把任务异常包装成 ExecutionException,调用方应决定记录、重试还是向上抛出。
ExecutorService.execute:只提交无结果任务
execute 没有 Future,也没有统一的返回结果;未捕获异常会交给线程的 UncaughtExceptionHandler。
// 说明:ExecutorService.execute:只提交无结果任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExecuteDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
// 作用:ExecutorService executor = Executors.newSingleThreadExecutor();;只提交无结果任务。
// 初始状态:executor = Executors.newSingleThreadExecutor()。
try {
executor.execute(() -> {
// 作用:executor.execute(() -> {;只提交无结果任务。
System.out.println("execute once");
// 输出:execute once
});
} finally {
executor.shutdown();
// 作用:executor.shutdown();;只提交无结果任务。
executor.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS);
// 作用:executor.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS);;只提交无结果任务。
}
}
}需要观察任务成功、失败或取消时优先使用 submit 并检查 Future。
ScheduledExecutorService:延迟或周期调度
scheduleAtFixedRate 按计划时间尝试执行,scheduleWithFixedDelay 按上次执行结束后再等待;周期任务抛出未捕获异常后通常不会继续调度,生产代码要明确处理异常。
// 说明:ScheduledExecutorService:延迟或周期调度。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorDemo {
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// 初始状态:scheduler = Executors.newScheduledThreadPool(1)。
// 作用:ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);;延迟或周期调度。
try {
scheduler.schedule(() -> {
// 作用:scheduler.schedule(() -> {;延迟或周期调度。
System.out.println("delayed");
// 输出:delayed
}, 10, TimeUnit.MILLISECONDS);
scheduler.shutdown();
// 作用:scheduler.shutdown();;延迟或周期调度。
scheduler.awaitTermination(1, TimeUnit.SECONDS);
// 作用:scheduler.awaitTermination(1, TimeUnit.SECONDS);;延迟或周期调度。
} finally {
scheduler.shutdownNow();
// 作用:scheduler.shutdownNow();;延迟或周期调度。
}
}
}有序关闭执行器:拒绝新任务并等待收尾
先 shutdown() 停止接收新任务,再等待已有任务;超时后用 shutdownNow() 请求中断。
// 说明:有序关闭执行器:拒绝新任务并等待收尾。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ExecutorShutdownDemo {
static void close(ExecutorService executor) throws InterruptedException {
executor.shutdown();
// 初始状态:executor.shutdown()。
// 作用:executor.shutdown();;拒绝新任务并等待收尾。
if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
executor.shutdownNow();
// 初始状态:executor.shutdownNow()。
// 作用:executor.shutdownNow();;拒绝新任务并等待收尾。
if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
throw new IllegalStateException("executor did not stop");
// 初始状态:throw new IllegalStateException("executor did not stop")。
// 作用:throw new IllegalStateException("executor did not stop");;拒绝新任务并等待收尾。
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1);
// 初始状态:executor = Executors.newFixedThreadPool(1)。
// 作用:ExecutorService executor = Executors.newFixedThreadPool(1);;拒绝新任务并等待收尾。
close(executor);
System.out.println("closed=" + executor.isTerminated());
// 输出:closed=true
}
}无论任务是否响应中断,都应该记录未完成任务并避免静默丢失。
不常用但需要知道
Thread.UncaughtExceptionHandler:观察 execute 任务异常
这是最后一道异常观察边界,不替代任务内部的业务错误处理。在线程池中应给线程设置统一 ThreadFactory,或者通过 Future 读取 submit 任务的异常。
// 作用:通过 Thread.UncaughtExceptionHandler 观察 execute 任务异常。
public class UncaughtExceptionDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> { throw new IllegalStateException("bad task"); }, "worker");
// 初始状态:thread = new Thread(() -> { throw new IllegalStateException("bad task"); }, "worker")。
// 作用:Thread thread = new Thread(() -> { throw new IllegalStateException("bad task"); }, "worker");;观察 execute 任务异常。
thread.setUncaughtExceptionHandler((t, error) -> {
// 作用:thread.setUncaughtExceptionHandler((t, error) -> {;观察 execute 任务异常。
System.out.println(t.getName() + ":" + error.getClass().getSimpleName());
// 输出:worker:IllegalStateException
});
thread.start();
// 作用:thread.start();;观察 execute 任务异常。
thread.join();
// 作用:thread.join();;观察 execute 任务异常。
}
}Thread.interrupt:协作式停止
被阻塞在 sleep、wait 或 join 的线程通常会收到 InterruptedException;捕获后要么结束任务,要么恢复中断标记。吞掉中断会让上层取消和关闭策略失效。
// 作用:通过 Thread.interrupt 协作式停止。
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
// 作用:Thread worker = new Thread(() -> {;协作式停止。
// 初始状态:worker = new Thread(() ->。
try {
Thread.sleep(10_000);
// 作用:Thread.sleep(10_000);;协作式停止。
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
// 初始状态:Thread.currentThread().interrupt()。
// 作用:Thread.currentThread().interrupt();;协作式停止。
System.out.println("cancelled");
// 输出:cancelled
}
});
worker.start();
// 作用:worker.start();;协作式停止。
worker.interrupt();
// 初始状态:worker.interrupt()。
// 作用:worker.interrupt();;协作式停止。
worker.join();
// 作用:worker.join();;协作式停止。
}
}取消任务:发出请求并检查取消状态
任务尚未开始时 cancel(false) 可以取消它;任务已经运行时,cancel(true) 只是发出中断请求,是否停止取决于任务是否正确响应中断。
import java.util.concurrent.FutureTask;
public class FutureCancelDemo {
public static void main(String[] args) {
FutureTask<String> task = new FutureTask<>(() -> "never started");
// 初始状态:task = new FutureTask<>(() -> "never started")。
// 作用:FutureTask<String> task = new FutureTask<>(() -> "never started");;发出请求并检查取消状态。
boolean cancelled = task.cancel(false);
// 初始状态:cancelled = task.cancel(false)。
// 作用:boolean cancelled = task.cancel(false);;发出请求并检查取消状态。
System.out.println(cancelled + ", " + task.isCancelled());
// 输出:true, true
}
}Thread.State:诊断线程状态
状态是诊断快照,不是业务同步工具。不要通过轮询 getState() 判断业务结果,应该使用 Future、CountDownLatch 或明确的消息协议。
// 作用:通过 Thread.State 诊断线程状态。
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> { });
// 初始状态:thread = new Thread(() -> { })。
// 作用:Thread thread = new Thread(() -> { });;诊断线程状态。
System.out.println(thread.getState());
// 输出:NEW
thread.start();
// 作用:thread.start();;诊断线程状态。
thread.join();
// 作用:thread.join();;诊断线程状态。
System.out.println(thread.getState());
// 输出:TERMINATED
}
}简单案例
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadBasicsDemo {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
// 初始状态:executor = Executors.newFixedThreadPool(2)。
// 作用:ExecutorService executor = Executors.newFixedThreadPool(2);;诊断线程状态。
try {
var futures = executor.invokeAll(List.of(
() -> "java",
() -> "concurrency"));
// 作用:var futures = executor.invokeAll(List.of(;诊断线程状态。
// 初始状态:futures = executor.invokeAll(List.of(。
System.out.println(futures.get(0).get() + " " + futures.get(1).get());
// 输出:java concurrency
} finally {
executor.shutdown();
// 作用:executor.shutdown();;诊断线程状态。
}
}
}invokeAll 适合需要等一批 Callable 都返回的简单场景;若需要逐个完成就处理 CompletionService,若只要最快结果则考虑 invokeAny。
易混点
start()与run()不同:前者启动新线程,后者只是普通方法调用。submit会把任务异常放进 Future,必须调用get()或检查状态;execute的异常走线程异常处理器。shutdown()不会中断正在运行的任务;shutdownNow()也只是协作式中断请求。sleep不释放锁,wait会释放当前对象监视器;两者用途不能互换。- 线程池大小、队列容量和拒绝策略要结合 CPU、阻塞比例和下游容量设置,不能只追求线程数。
课后小问
为什么直接调用
thread.run()看不到新线程效果? 答案:run()是普通方法调用,代码仍在当前线程执行。 解析:只有start()才会让 JVM 调度新的线程,并建立独立的线程栈。Future 超时后任务一定停止了吗? 答案:不一定,
get(timeout)只停止等待,任务可能仍在执行。 解析:若要请求取消,需要显式调用cancel(true),任务还必须响应中断。为什么执行器必须关闭? 答案:线程池中的工作线程和队列属于资源,未关闭可能让应用或测试进程无法退出。 解析:
shutdown给出有序结束边界,超时后再用shutdownNow做最后的协作式停止。
本节小结
- Runnable/Callable 描述任务,Thread 和 ExecutorService 提供执行资源。
- Future 负责结果、异常、取消和超时的观察,不会自动解决共享状态安全。
- 使用执行器时要明确线程池容量、任务异常、中断响应和关闭流程。
- 需要等待、批量提交或调度时优先使用对应的 Future、invokeAll 和 ScheduledExecutorService。
快速回顾
- 会用 Thread、Runnable 和
join()启动并等待线程。 - 会区分
execute、submit、invokeAll与invokeAny。 - 知道中断是协作式取消,并能正确恢复中断标记。
- 能写出
shutdown、等待、超时后shutdownNow的关闭流程。
阿源的知识库