一、为什么需要多线程?
在单线程世界中,任务只能顺序执行:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
scss
// 顺序执行,耗时 = task1 + task2
task1(); // 2秒
task2(); // 3秒
// 总耗时:5秒
而多线程允许任务并发执行:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
scss
// 并发执行,耗时 ≈ max(task1, task2)
new Thread(task1).start();
new Thread(task2).start();
// 总耗时:约3秒
✅ 多线程的核心价值: 充分利用多核 CPU,提升程序吞吐量与响应速度,尤其适用于 I/O 密集型和计算密集型任务。
二、Java 线程创建的 4 种方式
方式 | 核心接口/类 | 是否有返回值 | 是否推荐 |
---|---|---|---|
1. 继承 Thread 类 | Thread | 否 | ⚠️ 不推荐 |
2. 实现 Runnable 接口 | Runnable | 否 | ✅ 推荐(简单任务) |
3. 实现 Callable 接口 | Callable | 是 | ✅ 推荐(需返回值) |
4. 使用线程池 | ExecutorService | 可有可无 | ⭐⭐⭐⭐⭐ 强烈推荐 |
方式 1:继承 Thread
类(不推荐)
✅ 语法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
scala
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello from " + Thread.currentThread().getName());
}
}
// 使用
MyThread t = new MyThread();
t.start(); // 启动线程
❌ 缺点
- 违背"组合优于继承"原则 :
Thread
类承担了"线程"和"任务"双重职责。 - Java 不支持多继承:你的类无法再继承其他类。
- 任务与线程耦合:不利于线程的复用与管理。
✅ 仅用于教学演示,生产环境应避免。
方式 2:实现 Runnable
接口(推荐,简单任务)
✅ 语法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
scss
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
负责执行。 - 支持多实现:类可以实现多个接口。
- 符合单一职责原则。
✅ Lambda 优化
代码语言:javascript
代码运行次数:0
运行
AI代码解释
scss
new Thread(() -> System.out.println("Hello")).start();
✅ 适合无返回值、简单的一次性任务。
方式 3:实现 Callable
接口(推荐,需返回值)
✅ 语法
Callable
与 Runnable
类似,但:
call()
方法可返回值call()
方法可抛出异常
代码语言:javascript
代码运行次数:0
运行
AI代码解释
ini
import java.util.concurrent.*;
Callable<Integer> task = () -> {
System.out.println("Computing in " + Thread.currentThread().getName());
// 模拟计算
Thread.sleep(2000);
return 42; // 返回计算结果
};
✅ 如何获取返回值?------使用 Future
代码语言:javascript
代码运行次数:0
运行
AI代码解释
ini
// 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()
是阻塞调用,会一直等待直到任务完成。
方式 4:使用线程池(强烈推荐!)
✅ 为什么需要线程池?
直接创建线程的三大问题:
- 频繁创建/销毁线程开销大(内存、CPU)
- 无节制创建线程可能导致 OOM
- 缺乏统一管理
🔑 线程池的核心思想 : 预先创建一批线程 ,放入池中,任务来时直接分配,执行完后复用,避免重复创建。
✅ 核心 API:ExecutorService
代码语言:javascript
代码运行次数:0
运行
AI代码解释
csharp
// 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(); // 立即关闭
✅ 线程池的 7 大参数(ThreadPoolExecutor)
参数 | 说明 |
---|---|
corePoolSize | 核心线程数,即使空闲也保留 |
maximumPoolSize | 最大线程数 |
keepAliveTime | 非核心线程空闲存活时间 |
unit | 存活时间单位 |
workQueue | 任务队列(如 ArrayBlockingQueue, LinkedBlockingQueue) |
threadFactory | 线程创建工厂(可自定义线程名) |
handler | 拒绝策略(如 AbortPolicy, CallerRunsPolicy) |
❌ 为什么禁止使用 Executors
工厂方法?
阿里《Java 开发手册》明确禁止:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
ini
// ❌ 危险!可能 OOM
ExecutorService executor = Executors.newFixedThreadPool(10);
// 底层使用 LinkedBlockingQueue,无界队列!
ExecutorService executor2 = Executors.newCachedThreadPool();
// 最大线程数为 Integer.MAX_VALUE,可能创建过多线程!
✅ 正确做法 :手动创建
ThreadPoolExecutor
,明确配置队列大小和拒绝策略。
✅ 线程池的优势
- 降低资源消耗:复用线程,避免频繁创建销毁。
- 提高响应速度:任务到达后可立即执行。
- 统一管理:可监控、可配置、可拒绝。
- 提升可扩展性:通过调整参数优化性能。
三、高级玩法:CompletableFuture(JDK 8+)
对于复杂的异步编排,Future
过于简陋。CompletableFuture
提供了强大的函数式编程能力:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
rust
CompletableFuture.supplyAsync(() -> {
// 耗时计算
return fetchUserData();
})
.thenApply(user -> enrichUser(user)) // 转换结果
.thenAccept(enrichedUser -> saveToDB(enrichedUser)) // 消费结果
.exceptionally(throwable -> {
log.error("Async task failed", throwable);
return null;
});
✅ 支持链式调用、组合、异常处理,是现代异步编程的首选。
四、创建方式决策树

✅ 终极建议 :优先使用线程池 ,避免直接
new Thread
。
五、面试高频问题解析
❓1. Runnable 和 Callable 有什么区别?
答:
Runnable
:run()
无返回值,不能抛受检异常。Callable
:call()
有返回值,能抛异常。Callable
需配合Future
获取结果。
❓2. 为什么线程池能减少资源开销?
答 : 线程的创建和销毁涉及操作系统调用,开销大。 线程池通过复用已创建的线程,避免了重复的创建/销毁过程,显著降低了资源消耗。
❓3. submit() 和 execute() 的区别?
答:
execute(Runnable)
:来自Executor
,无返回值。submit(Runnable/Callable)
:来自ExecutorService
|hnhzmac.com|cdasheng.com|czhongxie.com,返回Future
,可获取结果或状态。
❓4. shutdown() 和 shutdownNow() 有什么区别?
答:
shutdown()
:平滑关闭,不再接收新任务,等待已提交任务执行完。shutdownNow()
:立即关闭,尝试停止所有正在执行的任务,返回未执行的任务列表。
❓5. 如何自定义线程池的线程名称?
答 :通过
ThreadFactory
:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
java
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 应用的第一步!