【创建线程的四种方式】

一、为什么需要多线程?

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

代码语言: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(); // 启动线程
❌ 缺点
  1. 违背"组合优于继承"原则Thread 类承担了"线程"和"任务"双重职责。
  2. Java 不支持多继承:你的类无法再继承其他类。
  3. 任务与线程耦合:不利于线程的复用与管理。

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


方式 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();
✅ 优势
  1. 解耦任务与线程Runnable 只定义任务逻辑,Thread 负责执行。
  2. 支持多实现:类可以实现多个接口。
  3. 符合单一职责原则
✅ Lambda 优化

代码语言:javascript

代码运行次数:0

运行


AI代码解释

scss 复制代码
new Thread(() -> System.out.println("Hello")).start();

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


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

✅ 语法

CallableRunnable 类似,但:

  • 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:使用线程池(强烈推荐!)

✅ 为什么需要线程池?

直接创建线程的三大问题

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

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


✅ 核心 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,明确配置队列大小和拒绝策略。


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

三、高级玩法: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 有什么区别?

  • Runnablerun() 无返回值,不能抛受检异常。
  • Callablecall() 有返回值,能抛异常。
  • 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 需要返回值的任务
线程池 所有生产环境场景 ⭐⭐⭐⭐⭐

记住

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

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

相关推荐
努力的小雨3 分钟前
10 个任务拆成 22 项能力,Seed 2.1 Pro 哪些方向值得用?
后端
步行cgn23 分钟前
Spring 基于 XML 的自动装配:byType 详解
java·后端·spring
步行cgn9 小时前
Spring c 命名空间注入详解
java·后端·spring
明月_清风9 小时前
Maven 到底是什么?一篇文章搞懂 Java 项目构建与依赖管理
java·后端·maven
明月_清风10 小时前
AI 越来越强,程序员真正的价值到底是什么?
人工智能·后端
aramae10 小时前
MySQL复合查询(8)
java·c语言·开发语言·后端·算法
Rain的Java大神之路10 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
码事漫谈12 小时前
别再跟AI说“请”了,它根本不 care——但有个东西它超在意
后端
郑州光合科技余经理13 小时前
同城外卖小程序开发:下单成功后,后台导出能不能对上用户端状态
开发语言·前端·git·后端·uni-app·php·ai编程
IT_陈寒14 小时前
Vue的响应式让我熬到凌晨三点,原来漏了这个小细节
前端·人工智能·后端