首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【创建线程的四种方式】

【创建线程的四种方式】

作者头像
艾伦耶格尔
发布2025-08-28 15:59:42
发布2025-08-28 15:59:42
17600
代码可运行
举报
文章被收录于专栏:Java基础Java基础
运行总次数:0
代码可运行

💡 摘要:你是否还在用 new Thread(new Runnable()) 创建线程? 是否在项目中看到 ExecutorService 却不知其优势? Java 提供了 4 种线程创建方式,每种背后都蕴含着不同的设计理念: 从最基础的 Thread 类,到优雅的 Callable,再到现代并发的基石——线程池。 本文将带你从零到实战,深入剖析每种方式的语法、适用场景、性能对比与核心陷阱**, 并结合 FutureCompletableFuture 等高级 API, 最后揭秘为什么阿里开发手册强制禁止使用 Executors 工厂方法。 文末附创建方式决策树面试高频问题,助你写出高效、可靠的并发代码。


一、为什么需要多线程?

在单线程世界中,任务只能顺序执行

代码语言:javascript
代码运行次数:0
运行
复制
// 顺序执行,耗时 = task1 + task2
task1(); // 2秒
task2(); // 3秒
// 总耗时:5秒

而多线程允许任务并发执行

代码语言:javascript
代码运行次数:0
运行
复制
// 并发执行,耗时 ≈ 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<V>

✅ 推荐(需返回值)

4. 使用线程池

ExecutorService

可有可无

⭐⭐⭐⭐⭐ 强烈推荐


方式 1:继承 Thread 类(不推荐)

✅ 语法
代码语言:javascript
代码运行次数:0
运行
复制
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello from " + Thread.currentThread().getName());
    }
}

// 使用
MyThread t = new MyThread();
t.start(); // 启动线程
❌ 缺点
  1. 违背“组合优于继承”原则Thread 类承担了“线程”和“任务”双重职责。
  2. Java 不支持多继承:你的类无法再继承其他类。
  3. 任务与线程耦合:不利于线程的复用与管理。

仅用于教学演示,生产环境应避免。


方式 2:实现 Runnable 接口(推荐,简单任务)

✅ 语法
代码语言:javascript
代码运行次数:0
运行
复制
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();
✅ 优势
  1. 解耦任务与线程Runnable 只定义任务逻辑,Thread 负责执行。
  2. 支持多实现:类可以实现多个接口。
  3. 符合单一职责原则
✅ Lambda 优化
代码语言:javascript
代码运行次数:0
运行
复制
new Thread(() -> System.out.println("Hello")).start();

适合无返回值、简单的一次性任务


方式 3:实现 Callable 接口(推荐,需返回值)

✅ 语法

CallableRunnable 类似,但:

  • call() 方法可返回值
  • call() 方法可抛出异常
代码语言:javascript
代码运行次数:0
运行
复制
import java.util.concurrent.*;

Callable<Integer> task = () -> {
    System.out.println("Computing in " + Thread.currentThread().getName());
    // 模拟计算
    Thread.sleep(2000);
    return 42; // 返回计算结果
};
✅ 如何获取返回值?——使用 Future
代码语言:javascript
代码运行次数:0
运行
复制
// 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:使用线程池(强烈推荐!)

✅ 为什么需要线程池?

直接创建线程的三大问题

  1. 频繁创建/销毁线程开销大(内存、CPU)
  2. 无节制创建线程可能导致 OOM
  3. 缺乏统一管理

🔑 线程池的核心思想预先创建一批线程,放入池中,任务来时直接分配,执行完后复用,避免重复创建。


✅ 核心 API:ExecutorService
代码语言:javascript
代码运行次数:0
运行
复制
// 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
运行
复制
// ❌ 危险!可能 OOM
ExecutorService executor = Executors.newFixedThreadPool(10); 
// 底层使用 LinkedBlockingQueue,无界队列!

ExecutorService executor2 = Executors.newCachedThreadPool();
// 最大线程数为 Integer.MAX_VALUE,可能创建过多线程!

正确做法手动创建 ThreadPoolExecutor,明确配置队列大小和拒绝策略。


✅ 线程池的优势
  1. 降低资源消耗:复用线程,避免频繁创建销毁。
  2. 提高响应速度:任务到达后可立即执行。
  3. 统一管理:可监控、可配置、可拒绝。
  4. 提升可扩展性:通过调整参数优化性能。

三、高级玩法:CompletableFuture(JDK 8+)

对于复杂的异步编排,Future 过于简陋。CompletableFuture 提供了强大的函数式编程能力:

代码语言:javascript
代码运行次数:0
运行
复制
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 有什么区别?

  • Runnablerun() 无返回值,不能抛受检异常。
  • Callablecall() 有返回值,能抛异常。
  • Callable 需配合 Future 获取结果。

❓2. 为什么线程池能减少资源开销?

: 线程的创建和销毁涉及操作系统调用,开销大。 线程池通过复用已创建的线程,避免了重复的创建/销毁过程,显著降低了资源消耗。


❓3. submit() 和 execute() 的区别?

  • execute(Runnable):来自 Executor,无返回值。
  • submit(Runnable/Callable):来自 ExecutorService,返回 Future,可获取结果或状态。

❓4. shutdown() 和 shutdownNow() 有什么区别?

  • shutdown():平滑关闭,不再接收新任务,等待已提交任务执行完。
  • shutdownNow():立即关闭,尝试停止所有正在执行的任务,返回未执行的任务列表。

❓5. 如何自定义线程池的线程名称?

:通过 ThreadFactory

代码语言:javascript
代码运行次数:0
运行
复制
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

需要返回值的任务

线程池

所有生产环境场景

⭐⭐⭐⭐⭐

记住

  1. 永远不要在生产环境直接 new Thread()
  2. 优先使用 ThreadPoolExecutor 手动配置线程池
  3. 复杂异步使用 CompletableFuture
  4. 用完线程池记得 shutdown()

掌握线程创建的正确姿势,是写出高性能、高可靠 Java 应用的第一步!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、为什么需要多线程?
  • 二、Java 线程创建的 4 种方式
    • 方式 1:继承 Thread 类(不推荐)
      • ✅ 语法
      • ❌ 缺点
    • 方式 2:实现 Runnable 接口(推荐,简单任务)
      • ✅ 语法
      • ✅ 优势
      • ✅ Lambda 优化
    • 方式 3:实现 Callable 接口(推荐,需返回值)
      • ✅ 语法
      • ✅ 如何获取返回值?——使用 Future
      • ✅ 优势
    • 方式 4:使用线程池(强烈推荐!)
      • ✅ 为什么需要线程池?
      • ✅ 核心 API:ExecutorService
      • ✅ 线程池的 7 大参数(ThreadPoolExecutor)
      • ❌ 为什么禁止使用 Executors 工厂方法?
      • ✅ 线程池的优势
  • 三、高级玩法:CompletableFuture(JDK 8+)
  • 四、创建方式决策树
  • 五、面试高频问题解析
    • ❓1. Runnable 和 Callable 有什么区别?
    • ❓2. 为什么线程池能减少资源开销?
    • ❓3. submit() 和 execute() 的区别?
    • ❓4. shutdown() 和 shutdownNow() 有什么区别?
    • ❓5. 如何自定义线程池的线程名称?
  • 六、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档