💡 摘要:你是否还在用
new Thread(new Runnable())
创建线程? 是否在项目中看到ExecutorService
却不知其优势? Java 提供了 4 种线程创建方式,每种背后都蕴含着不同的设计理念: 从最基础的Thread
类,到优雅的Callable
,再到现代并发的基石——线程池。 本文将带你从零到实战,深入剖析每种方式的语法、适用场景、性能对比与核心陷阱**, 并结合Future
、CompletableFuture
等高级 API, 最后揭秘为什么阿里开发手册强制禁止使用Executors
工厂方法。 文末附创建方式决策树与面试高频问题,助你写出高效、可靠的并发代码。
在单线程世界中,任务只能顺序执行:
// 顺序执行,耗时 = task1 + task2
task1(); // 2秒
task2(); // 3秒
// 总耗时:5秒
而多线程允许任务并发执行:
// 并发执行,耗时 ≈ max(task1, task2)
new Thread(task1).start();
new Thread(task2).start();
// 总耗时:约3秒
✅ 多线程的核心价值: 充分利用多核 CPU,提升程序吞吐量与响应速度,尤其适用于 I/O 密集型和计算密集型任务。
方式 | 核心接口/类 | 是否有返回值 | 是否推荐 |
---|---|---|---|
1. 继承 Thread 类 | Thread | 否 | ⚠️ 不推荐 |
2. 实现 Runnable 接口 | Runnable | 否 | ✅ 推荐(简单任务) |
3. 实现 Callable 接口 | Callable<V> | 是 | ✅ 推荐(需返回值) |
4. 使用线程池 | ExecutorService | 可有可无 | ⭐⭐⭐⭐⭐ 强烈推荐 |
Thread
类(不推荐)class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello from " + Thread.currentThread().getName());
}
}
// 使用
MyThread t = new MyThread();
t.start(); // 启动线程
Thread
类承担了“线程”和“任务”双重职责。✅ 仅用于教学演示,生产环境应避免。
Runnable
接口(推荐,简单任务)Runnable task = () -> {
System.out.println("Task running in " + Thread.currentThread().getName());
// 模拟耗时操作
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("Task done");
};
// 创建线程并启动
Thread t = new Thread(task);
t.start();
Runnable
只定义任务逻辑,Thread
负责执行。new Thread(() -> System.out.println("Hello")).start();
✅ 适合无返回值、简单的一次性任务。
Callable
接口(推荐,需返回值)Callable
与 Runnable
类似,但:
call()
方法可返回值call()
方法可抛出异常import java.util.concurrent.*;
Callable<Integer> task = () -> {
System.out.println("Computing in " + Thread.currentThread().getName());
// 模拟计算
Thread.sleep(2000);
return 42; // 返回计算结果
};
Future
// 1. 需要 FutureTask 包装(底层)
FutureTask<Integer> futureTask = new FutureTask<>(task);
// 2. 用 Thread 执行
Thread t = new Thread(futureTask);
t.start();
// 3. 获取结果(阻塞直到完成)
try {
Integer result = futureTask.get(); // 阻塞
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
⚠️ 注意:
future.get()
是阻塞调用,会一直等待直到任务完成。
直接创建线程的三大问题:
🔑 线程池的核心思想: 预先创建一批线程,放入池中,任务来时直接分配,执行完后复用,避免重复创建。
ExecutorService
// 1. 创建线程池(推荐手动配置)
ExecutorService executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), // 任务队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
// 2. 提交任务
// 无返回值
executor.submit(() -> System.out.println("Hello from pool"));
// 有返回值
Future<Integer> future = executor.submit(() -> {
return 1 + 1;
});
// 3. 获取结果
try {
Integer result = future.get(3, TimeUnit.SECONDS); // 支持超时
System.out.println("Result: " + result);
} catch (TimeoutException e) {
System.out.println("Task timeout");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 4. 关闭线程池(重要!)
executor.shutdown(); // 平滑关闭
// 或 executor.shutdownNow(); // 立即关闭
参数 | 说明 |
---|---|
corePoolSize | 核心线程数,即使空闲也保留 |
maximumPoolSize | 最大线程数 |
keepAliveTime | 非核心线程空闲存活时间 |
unit | 存活时间单位 |
workQueue | 任务队列(如 ArrayBlockingQueue, LinkedBlockingQueue) |
threadFactory | 线程创建工厂(可自定义线程名) |
handler | 拒绝策略(如 AbortPolicy, CallerRunsPolicy) |
Executors
工厂方法?阿里《Java 开发手册》明确禁止:
// ❌ 危险!可能 OOM
ExecutorService executor = Executors.newFixedThreadPool(10);
// 底层使用 LinkedBlockingQueue,无界队列!
ExecutorService executor2 = Executors.newCachedThreadPool();
// 最大线程数为 Integer.MAX_VALUE,可能创建过多线程!
✅ 正确做法:手动创建
ThreadPoolExecutor
,明确配置队列大小和拒绝策略。
对于复杂的异步编排,Future
过于简陋。CompletableFuture
提供了强大的函数式编程能力:
CompletableFuture.supplyAsync(() -> {
// 耗时计算
return fetchUserData();
})
.thenApply(user -> enrichUser(user)) // 转换结果
.thenAccept(enrichedUser -> saveToDB(enrichedUser)) // 消费结果
.exceptionally(throwable -> {
log.error("Async task failed", throwable);
return null;
});
✅ 支持链式调用、组合、异常处理,是现代异步编程的首选。
✅ 终极建议:优先使用线程池,避免直接
new Thread
。
答:
Runnable
:run()
无返回值,不能抛受检异常。Callable
:call()
有返回值,能抛异常。Callable
需配合 Future
获取结果。答: 线程的创建和销毁涉及操作系统调用,开销大。 线程池通过复用已创建的线程,避免了重复的创建/销毁过程,显著降低了资源消耗。
答:
execute(Runnable)
:来自 Executor
,无返回值。submit(Runnable/Callable)
:来自 ExecutorService
,返回 Future
,可获取结果或状态。答:
shutdown()
:平滑关闭,不再接收新任务,等待已提交任务执行完。shutdownNow()
:立即关闭,尝试停止所有正在执行的任务,返回未执行的任务列表。答:通过
ThreadFactory
:
ThreadFactory factory = new ThreadFactory() {
private AtomicInteger counter = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "MyPool-Thread-" + counter.incrementAndGet());
}
};
方式 | 适用场景 | 推荐度 |
---|---|---|
继承 Thread | 教学演示 | ⚠️ 避免 |
实现 Runnable | 简单无返回值任务 | ✅ |
实现 Callable | 需要返回值的任务 | ✅ |
线程池 | 所有生产环境场景 | ⭐⭐⭐⭐⭐ |
✅ 记住:
new Thread()
ThreadPoolExecutor
手动配置线程池CompletableFuture
shutdown()
掌握线程创建的正确姿势,是写出高性能、高可靠 Java 应用的第一步!