【创建线程的四种方式】

一、为什么需要多线程?

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

代码语言: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 应用的第一步!

相关推荐
华仔啊21 分钟前
为什么现代 Node 后端都选 NestJS + TypeScript?这组合真香了
javascript·后端
Joey_Chen26 分钟前
【Golang开发】快速入门Go——Go语言中的面向对象编程
后端·go
lookFlying28 分钟前
Python 项目 Docker 仓库发布指南
后端
易元28 分钟前
模式组合应用-组合模式
后端·设计模式
秋难降34 分钟前
从浅克隆到深克隆:原型模式如何解决对象创建的 “老大难”?😘
后端·设计模式·程序员
bobz96544 分钟前
安装 nvidia 驱动之前要求关闭 secureBoot 么
后端
程序员的世界你不懂1 小时前
【Flask】测试平台开发实战-第一篇
后端·python·flask
bobz9651 小时前
dracut 是什么?
后端
自由的疯3 小时前
Java RuoYi整合Magic-Api详解
java·后端·架构
自由的疯3 小时前
Java 实现TXT文件上传并解析的Spring Boot应用
后端·架构