AI 创作标识
本文由人工智能(AI)辅助创作/生成,内容仅供学习与参考,不保证完全准确,请结合官方文档与实际情况核对。
Java 中线程的创建方式:从基础到现代

引言
在 Java 中,线程是并发编程的基本执行单元。创建一个线程看似简单,但 Java 发展近三十年来,围绕"如何创建线程"这一问题,提供了多种不同层次、不同风格的 API。从最原始的继承 Thread 类,到 JDK 5 的线程池、JDK 8 的 CompletableFuture,再到 JDK 21 的虚拟线程,每一种方式都对应着特定的使用场景和设计哲学。
本文系统梳理 Java 中创建线程的各种方式,涵盖其 API 用法、底层原理、适用场景以及相互之间的差异。
一、继承 Thread 类
1.1 基本用法
最基础的方式是继承 java.lang.Thread 类,重写 run() 方法:
java
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行中:" + Thread.currentThread().getName());
}
}
// 使用
MyThread t = new MyThread();
t.start();
1.2 原理
Thread 类本身实现了 Runnable 接口。调用 start() 方法时,JVM 会通过本地方法 start0() 创建一个操作系统线程,新线程随后执行 run() 方法。注意:直接调用 run() 只是在当前线程中执行方法体,不会创建新线程。
1.3 局限
由于 Java 不支持多继承,继承 Thread 后无法再继承其他类。此外,任务逻辑与线程机制耦合在一起,不利于复用。因此这种方式通常只用于简单的演示或测试。
二、实现 Runnable 接口
2.1 基本用法
实现 java.lang.Runnable 接口,将任务逻辑与线程机制分离:
java
public class MyTask implements Runnable {
@Override
public void run() {
System.out.println("任务执行中:" + Thread.currentThread().getName());
}
}
// 使用
Thread t = new Thread(new MyTask());
t.start();
2.2 Lambda 简化
由于 Runnable 是函数式接口,JDK 8 之后可以用 Lambda 表达式简化:
java
Thread t = new Thread(() -> System.out.println("运行中"));
t.start();
2.3 优势
任务与线程解耦,一个 Runnable 实例可以被多个线程共享执行,也可以配合线程池使用。这是最推荐的"裸线程"创建方式。
三、实现 Callable 接口 + FutureTask
3.1 基本用法
Runnable 的 run() 方法没有返回值,也无法抛出受检异常。java.util.concurrent.Callable 解决了这两个问题:
java
Callable<String> task = () -> {
Thread.sleep(1000);
return "执行结果";
};
FutureTask<String> futureTask = new FutureTask<>(task);
Thread t = new Thread(futureTask);
t.start();
// 获取返回值(阻塞直到完成)
String result = futureTask.get();
3.2 原理
FutureTask 同时实现了 Runnable 和 Future 接口。它包装 Callable,在 run() 中调用 call() 并将结果保存,get() 方法用于获取结果或捕获异常。
3.3 适用场景
需要获取任务执行结果或需要处理受检异常时,使用 Callable + FutureTask。但在实际工程中,通常直接配合线程池使用,而非手动创建 Thread。
四、使用 ExecutorService 线程池
4.1 基本用法
JDK 5 引入的 ExecutorService 是创建和管理线程的推荐方式:
java
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<String> future = executor.submit(() -> {
return "任务结果";
});
String result = future.get();
executor.shutdown();
4.2 常用工厂方法
Executors 工具类提供了多种线程池:
| 方法 | 说明 |
|---|---|
newFixedThreadPool(n) |
固定大小线程池 |
newCachedThreadPool() |
可缓存线程池,空闲线程回收,需求时新建 |
newSingleThreadExecutor() |
单线程池,保证任务顺序执行 |
newScheduledThreadPool(n) |
支持定时和周期性任务 |
newWorkStealingPool() |
基于 Fork/Join 的工作窃取线程池 |
4.3 手动创建 ThreadPoolExecutor
Executors 的工厂方法在大型应用中存在资源耗尽风险,更推荐手动构造 ThreadPoolExecutor,明确指定核心线程数、最大线程数、队列容量和拒绝策略:
java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadFactory() { // 自定义线程工厂
private final AtomicInteger count = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "worker-" + count.getAndIncrement());
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
4.4 优势
线程池复用线程,避免频繁创建和销毁的开销;通过队列和拒绝策略控制资源使用;提供 Future、批量提交、超时控制等能力。这是传统平台线程场景下最主流的线程创建方式。
五、使用 CompletableFuture
5.1 基本用法
JDK 8 引入的 CompletableFuture 支持异步任务编排,默认使用 ForkJoinPool.commonPool():
java
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "异步结果";
});
future.thenApply(String::toUpperCase)
.thenAccept(System.out::println);
5.2 指定线程池
可以传入自定义 Executor:
java
ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture.supplyAsync(() -> "结果", executor)
.thenAcceptAsync(System.out::println, executor);
5.3 适用场景
适合编排多个异步任务之间的依赖关系,如串行、并行、组合、超时等。CompletableFuture 本身不是线程创建机制,而是任务编排工具,但它在底层依赖线程池来执行任务。
六、使用 ForkJoinPool
6.1 基本用法
JDK 7 引入的 ForkJoinPool 专为分治任务设计,核心是工作窃取算法:
java
ForkJoinPool pool = new ForkJoinPool();
class SumTask extends RecursiveTask<Long> {
private final long[] array;
private final int start, end;
SumTask(long[] array, int start, int end) {
this.array = array; this.start = start; this.end = end;
}
@Override
protected Long compute() {
if (end - start <= 1000) {
long sum = 0;
for (int i = start; i < end; i++) sum += array[i];
return sum;
}
int mid = (start + end) / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork();
return right.compute() + left.join();
}
}
Long result = pool.invoke(new SumTask(array, 0, array.length));
6.2 适用场景
适合可以递归拆分的计算密集型任务,如归并排序、大规模数值计算。Java 8 的并行流(parallelStream())底层也使用 ForkJoinPool.commonPool()。
6.3 与线程池的差异
ForkJoinPool 的每个工作线程维护一个双端队列,空闲线程从其他线程的队列尾部窃取任务。这种设计在分治场景下能最大化 CPU 利用率,但对普通 I/O 任务并不合适。
七、虚拟线程(JDK 21+)
7.1 基本用法
JDK 21 正式引入虚拟线程,创建方式与平台线程高度一致:
java
// 方式一:直接启动
Thread.startVirtualThread(() -> System.out.println("虚拟线程"));
// 方式二:Thread.Builder
Thread t = Thread.ofVirtual()
.name("my-virtual")
.start(() -> System.out.println("虚拟线程"));
// 方式三:虚拟线程执行器
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> handleRequest());
}
7.2 原理
虚拟线程由 JVM 管理,多个虚拟线程复用一个平台线程(载体线程)。当虚拟线程执行阻塞操作时,JVM 将其栈帧保存到堆中并卸载,载体线程转而执行其他虚拟线程。这避免了操作系统线程上下文切换的开销。
7.3 适用场景
虚拟线程适合 I/O 密集型场景,如 Web 服务处理大量并发请求。对于 CPU 密集型任务,虚拟线程不会带来收益,因为载体线程数量仍受限于 CPU 核心数。
7.4 注意事项
- 虚拟线程是守护线程,不能设置为非守护线程。
- 虚拟线程不支持线程优先级。
- 虚拟线程不应被池化,每个任务创建一个虚拟线程即可。
- JDK 24 的 JEP 491 解决了虚拟线程在
synchronized块中阻塞时固定载体线程的问题。
八、Thread.Builder API(JDK 21+)
8.1 基本用法
JDK 21 引入了 Thread.Builder 接口,提供统一的线程创建方式,同时支持平台线程和虚拟线程:
java
// 平台线程
Thread platformThread = Thread.ofPlatform()
.name("platform-", 1)
.priority(Thread.NORM_PRIORITY)
.unstarted(runnable);
// 虚拟线程
Thread virtualThread = Thread.ofVirtual()
.name("virtual-", 1)
.unstarted(runnable);
8.2 ThreadFactory
Thread.Builder 也可以作为 ThreadFactory 使用:
java
ThreadFactory factory = Thread.ofVirtual().name("worker-", 0).factory();
ExecutorService executor = Executors.newThreadPerTaskExecutor(factory);
8.3 优势
Thread.Builder 统一了平台线程和虚拟线程的创建接口,支持链式配置名称、优先级、守护状态、未捕获异常处理器等属性,是 JDK 21 之后推荐的线程创建方式。
九、其他创建方式
9.1 Timer 与 TimerTask
java.util.Timer 可以在后台线程中调度任务:
java
Timer timer = new Timer("timer-thread", true);
timer.schedule(new TimerTask() {
public void run() {
System.out.println("定时任务");
}
}, 1000, 2000);
Timer 内部只有一个线程,任务顺序执行,一个任务异常会导致整个 Timer 终止。JDK 5 之后推荐使用 ScheduledExecutorService 替代。
9.2 SwingWorker
在 Swing 桌面应用中,SwingWorker 用于在后台线程执行耗时任务,并在事件分发线程(EDT)中更新 UI:
java
new SwingWorker<String, Void>() {
protected String doInBackground() {
return longRunningTask();
}
protected void done() {
try { label.setText(get()); } catch (Exception e) { }
}
}.execute();
SwingWorker 内部使用线程池管理后台线程,doInBackground() 在工作线程执行,done() 在 EDT 执行。
9.3 parallelStream
Java 8 的并行流通过 ForkJoinPool.commonPool() 执行任务:
java
list.parallelStream()
.map(this::process)
.collect(Collectors.toList());
并行流不是直接创建线程的方式,而是一种声明式的并行执行机制,底层复用 Fork/Join 框架。
9.4 匿名内部类与 Lambda
严格来说,匿名内部类和 Lambda 不是独立的线程创建方式,而是 Thread 或 Runnable 的语法糖:
java
// 匿名内部类
new Thread(new Runnable() {
public void run() { }
}).start();
// Lambda
new Thread(() -> { }).start();
十、各种方式对比
| 方式 | 返回值 | 异常处理 | 线程复用 | 适用场景 |
|---|---|---|---|---|
| 继承 Thread | 无 | 无受检异常 | 否 | 简单演示 |
| 实现 Runnable | 无 | 无受检异常 | 否 | 任务与线程解耦 |
| Callable + FutureTask | 有 | 可抛受检异常 | 否 | 需要返回值和异常 |
| ExecutorService | 有(Future) | 通过 Future | 是 | 通用并发任务 |
| CompletableFuture | 有 | 链式处理 | 是(底层线程池) | 异步任务编排 |
| ForkJoinPool | 有 | 通过 Future | 是 | 分治计算任务 |
| 虚拟线程 | 有 | 同平台线程 | 不需要池化 | I/O 密集型高并发 |
| Thread.Builder | 有 | 同平台线程 | 取决于类型 | JDK 21+ 统一接口 |
| Timer | 无 | 无受检异常 | 单线程 | 简单定时任务 |
| SwingWorker | 有 | 通过 get() | 是 | Swing 后台任务 |
十一、选型建议
- 简单任务 :优先使用
Runnable+Thread,或直接使用线程池。 - 需要返回值 :使用
Callable+ExecutorService.submit()。 - 异步编排 :使用
CompletableFuture。 - 分治计算 :使用
ForkJoinPool或并行流。 - 高并发 I/O:使用虚拟线程(JDK 21+)。
- 定时任务 :使用
ScheduledExecutorService,而非Timer。 - 统一创建接口 :JDK 21+ 优先使用
Thread.ofPlatform()/Thread.ofVirtual()。
无论选择哪种方式,核心原则是:任务逻辑与线程机制分离,线程资源通过池化管理,根据任务类型(CPU 密集 / I/O 密集)选择合适的并发模型。
十二、总结
Java 提供了从底层到高层的多种线程创建方式,反映了并发编程抽象层次的不断提升:
- JDK 1.0 :
Thread和Runnable提供了最原始的创建能力。 - JDK 5 :
ExecutorService将线程管理标准化,线程池成为主流。 - JDK 7 :
ForkJoinPool为分治任务提供了专用框架。 - JDK 8 :
CompletableFuture将异步编程声明式化。 - JDK 21 :虚拟线程和
Thread.Builder重新定义了线程的创建和管理方式。
理解每种方式的原理和适用场景,有助于在实际开发中做出合理的技术选择,写出高效、可维护的并发程序。
AI 创作标识:本文由人工智能生成,仅供学习与参考。