创建线程有几种方式?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官提出这个问题,通常旨在考察以下几个方面:

  1. 对 Java 线程基础知识的掌握程度:你是否清楚创建线程最核心的几种方式。
  2. 对不同创建方式原理的理解深度:你是否理解 ThreadRunnableCallable 之间的关系与区别,以及线程池的本质。
  3. 对并发编程发展趋势与最佳实践的认知:你是否了解为什么现在不推荐直接 new Thread(),而推荐使用线程池。
  4. 实际应用与场景选择能力:你能否根据不同的业务场景(如需要返回值、需要管理大量线程)选择合适的创建方式。

核心答案

从源码和 API 层面看,创建线程的本质方式只有一种:实例化 java.lang.Thread 对象并调用其 start() 方法

但根据 Thread 对象执行的任务来源不同,通常将其分为 四种经典实现方式

  1. 继承 Thread,重写 run() 方法。
  2. 实现 Runnable 接口,将实例作为 Thread 的构造参数。
  3. 实现 Callable 接口,结合 FutureTask 作为 Thread 的构造参数。
  4. 通过线程池(如 ExecutorService)创建并管理线程

此外,内部类、Lambda 表达式等方式只是上述方式的语法糖或便捷写法。

深度解析

原理/机制

  • Thread 类本身实现了 Runnable 接口。方式一(继承)是直接将任务逻辑定义在 Thread 子类中;方式二和三是将任务逻辑(Runnable.run()Callable.call())与线程本身(Thread解耦,这是更推荐的做法,因为它更灵活,符合组合优于继承的原则。
  • CallableRunnable 的核心区别在于:Callable.call() 可以返回结果和抛出受检异常,而 Runnable.run() 不行。FutureTask 实现了 RunnableFuture 接口,它同时包装了 Callable 任务并提供了对任务执行结果的异步获取(get())和取消等能力。
  • 线程池是生产实践中的绝对主流。它通过复用已创建的线程来避免频繁“创建-销毁”线程的巨大开销,并能提供强大的任务调度、管理和监控能力。

代码示例

// 1. 继承 Thread 类
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread is running.");
    }
}
new MyThread().start();

// 2. 实现 Runnable 接口
Runnable task = () -> System.out.println("Runnable task is running.");
new Thread(task).start();

// 3. 实现 Callable 接口
Callable<Integer> callableTask = () -> {
    Thread.sleep(1000);
    return 42;
};
FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
new Thread(futureTask).start();
Integer result = futureTask.get(); // 阻塞获取结果

// 4. 通过线程池创建(核心方式)
ExecutorService executor = Executors.newFixedThreadPool(4);
// 提交 Runnable 任务
executor.submit(() -> System.out.println("Task from thread pool."));
// 提交 Callable 任务
Future<Integer> future = executor.submit(callableTask);
executor.shutdown(); // 重要:使用后关闭

对比分析与最佳实践

  • Runnable vs Callable:需要任务返回值或处理任务执行中的受检异常时,使用 Callable;否则使用 Runnable
  • 直接 new Thread() vs 线程池在绝大多数生产场景中,应禁止直接创建 Thread 对象。原因包括:
    • a) 资源消耗大(创建/销毁开销);
    • b) 管理困难(无法限制数量、无统一调度);
    • c) 功能薄弱(缺乏执行结果处理、定期执行等功能)。
    • 务必使用 ThreadPoolExecutor 或其便捷工厂类(如 Executors.newFixedThreadPool)来创建线程池,并根据业务场景合理配置核心参数。

常见误区

  • 误以为调用 run() 方法就能启动新线程。实际上,run() 只是普通方法调用,start() 才会让 JVM 创建新的操作系统线程并回调 run()
  • 认为 Executors.newCachedThreadPool()newSingleThreadExecutor() 是万能的。前者可能导致线程数失控,后者可能堆积大量任务导致 OOM。关键场景下应直接使用 ThreadPoolExecutor 构造方法,明确指定核心参数(核心线程数、最大线程数、队列等)

总结

创建线程的核心是实例化 Thread 类,但根据任务定义方式可分为继承 Thread、实现 Runnable、实现 Callable 及使用线程池四种方式;现代 Java 并发编程中,使用并正确配置线程池是唯一推荐的生产实践