Java 中线程的创建方式:从基础到现代

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 创作标识:本文由人工智能生成,仅供学习与参考。

相关推荐
蚰蜒螟3 小时前
从内核启动到 Java NIO:一次 socket accept 的源码串联之旅
java·python·nio
学逆向的3 小时前
C 突破编译器限制
c语言·开发语言·网络安全
会编程的吕洞宾3 小时前
Spring AI 2.0 自校正结构化输出实战:让大模型稳定吐出合格 JSON
java·人工智能·后端·spring
bmxy小明同学3 小时前
2026-10-09-token与成本
java·前端·数据库
jimy13 小时前
C++ 虚函数表:基类和派生类共用一张 vtable 吗?
开发语言·c++
爱吃提升3 小时前
Dev-C++ 5.11 C++断点调试完整教程
开发语言·c++
夜晚回家4 小时前
Python 原始字符串详解 | r 前缀到底让什么不再转义
开发语言·python
陈年老古董4 小时前
PyQt5 入门指南:环境搭建、PyCharm 外部工具配置与登录界面实战
开发语言·qt·pycharm
看浪的路人4 小时前
第7讲:大模型后端服务化——Go 实现高并发 LLM Proxy / Router / Fallback
开发语言·后端·golang
微笑的曙光4 小时前
第八期 · 情绪闯关挑战盒:一个「无惩罚」成就系统的数据建模
java·微信小程序