【Java 并发编程】线程创建 6 种方式:Thread/Runnable/Callable 核心类全解析
引言:线程与Java并发的核心
在Java中,线程是实现并发编程的基础单元,它允许程序在同一时间执行多个任务(如后台处理、异步通信等)。Java提供了多种创建线程的方式,每种方式都有其设计初衷、适用场景和优缺点。本文将以总分总结构,详细拆解Java中创建线程的6种核心方式,包括原理剖析、代码实战、注意事项,并通过流程图辅助理解,帮助你彻底掌握线程创建的底层逻辑与实践技巧。
文章目录
- 引言:线程与Java并发的核心
- 一、继承Thread类(最基础的线程创建方式)
- 1. 原理剖析
- 2. 实现步骤
- 3. 完整代码示例
- 注意:输出顺序是由每个线程自己抢的,不是固定的。
- 4. 关键注意点:`start()` vs `run()`
- 5. 优缺点分析
- 二、实现Runnable接口(解耦首选方式)
- 1. 原理剖析
- 2. 实现步骤
- 3. 完整代码示例
- 4. 核心优势:任务复用与解耦
- 5. 与Thread类的对比
- 6. 优缺点分析
- 三、实现Callable接口(带返回值的线程)
- 1. 原理剖析
- (1)Callable接口定义
- (2)FutureTask的桥梁作用
- (3)核心关系流程图
- 2. 实现步骤
- 3. 完整代码示例
- 4. 关键注意点
- 5. 优缺点分析
- 四、使用线程池(高并发场景必备)
- 1. 原理剖析
- (1)线程池的核心组件
- (2)线程池工作流程
- (3)核心参数解析
- 2. 线程池的创建方式
- (1)方式1:通过Executors工具类创建
- (2)方式2:直接创建ThreadPoolExecutor(推荐)
- 3. 关键注意点
- 4. 优缺点分析
- 五、CompletableFuture(Java 8+异步神器)
- 1. 原理剖析
- 2. 核心方法分类
- 3. 完整代码示例
- (1)示例1:无返回值的异步任务
- (2)示例2:有返回值的异步任务+链式回调
- 4. 核心优势:非阻塞与链式编程
- 5. 优缺点分析
- 六、总结:如何选择合适的线程创建方式?
- 关键建议
一、继承Thread类(最基础的线程创建方式)
Thread是Java中封装线程操作的核心类,它本身实现了Runnable接口。通过继承Thread类并重写run()方法,可以定义线程的执行逻辑,这是最基础的线程创建方式。
1. 原理剖析
Thread类的核心作用:封装了线程的生命周期(新建、就绪、运行、阻塞、终止)和底层操作系统调用(如启动线程、中断线程)。- 线程执行逻辑的载体:
run()方法是线程的“任务入口”,当线程启动后,JVM会自动调用该方法执行任务;若未重写run(),则会执行父类Thread的默认实现(无实际逻辑)。
2. 实现步骤
- 定义自定义类,继承
Thread类; - 重写
Thread类的run()方法,在方法体内编写线程要执行的任务逻辑; - 创建自定义类的实例(即线程对象);
- 调用线程对象的
start()方法,启动线程(注意:不可直接调用run()方法)。
3. 完整代码示例
/*** 方式1:继承Thread类创建线程*/
public class ThreadExtendDemo extends Thread {// 1. 重写run()方法,定义线程任务@Overridepublic void run() {// 线程要执行的逻辑:这里模拟循环打印for (int i = 0; i < 5; i++) {// Thread.currentThread().getName():获取当前线程名称System.out.println("[" + Thread.currentThread().getName() + "] 执行任务,i=" + i);try {// 模拟任务耗时:让线程休眠100ms,释放CPU资源Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {// 2. 创建线程实例(自定义Thread子类对象)ThreadExtendDemo thread1 = new ThreadExtendDemo();ThreadExtendDemo thread2 = new ThreadExtendDemo();// 可选:设置线程名称(便于调试)thread1.setName("线程A");thread2.setName("线程B");// 3. 调用start()方法启动线程(JVM会自动调用run())thread1.start();thread2.start();// 主线程逻辑:与子线程并发执行System.out.println("[" + Thread.currentThread().getName() + "] 主线程执行完毕");}
}
执行结果(部分):
[main] 主线程执行完毕
[线程A] 执行任务,i=0
[线程B] 执行任务,i=0
[线程A] 执行任务,i=1
[线程B] 执行任务,i=1
注意:输出顺序是由每个线程自己抢的,不是固定的。
4. 关键注意点:start() vs run()
很多初学者会直接调用run()方法,但这是错误的!二者的核心区别在于是否创建新线程:
graph TDA[调用 thread.start() ] --> B[JVM向操作系统申请新线程资源]B --> C[新线程启动后,自动调用 run() 方法]C --> D[任务在新线程中执行]E[直接调用 thread.run() ] --> F[无新线程创建,run() 作为普通方法执行]graph TDA[调用 thread.start() ] --> B[JVM向操作系统申请新线程资源]B --> C[新线程启动后,自动调用 run() 方法]C --> D[任务在新线程中执行]E[直接调用 thread.run() ] --> F[无新线程创建,run() 作为普通方法执行]
示例验证:若将上述代码中的thread1.start()改为thread1.run(),执行结果会变成“主线程先执行完run()逻辑,再执行主线程打印”,完全失去并发效果。
5. 优缺点分析
| 优点 | 缺点 |
|---|---|
| 实现简单,直接继承Thread即可 | 受Java单继承限制:若类已继承其他类(如Object外的类),则无法再继承Thread |
可直接通过this获取当前线程对象 | 任务与线程耦合:线程对象与任务逻辑绑定,无法复用线程执行不同任务 |
二、实现Runnable接口(解耦首选方式)
为解决Thread类的单继承限制,Java提供了Runnable接口——它仅定义了一个run()方法,代表线程要执行的任务。通过实现Runnable接口,可以将“线程对象”与“任务逻辑”解耦,是实际开发中更常用的方式。
1. 原理剖析
Runnable是一个函数式接口(Java 8+),定义如下:
@FunctionalInterface
public interface Runnable {void run(); // 仅包含任务逻辑,无返回值、不抛checked异常
}
- 核心逻辑:
Thread类有一个构造器Thread(Runnable target),可接收Runnable实例(任务)。当线程启动后,JVM会调用target.run(),从而实现“线程对象”与“任务”的分离。
2. 实现步骤
- 定义自定义类,实现
Runnable接口; - 重写
Runnable的run()方法,编写任务逻辑; - 创建
Runnable实例(任务对象); - 创建
Thread实例,将Runnable实例传入Thread构造器; - 调用
Thread实例的start()方法启动线程。
3. 完整代码示例
/*** 方式2:实现Runnable接口创建线程*/
public class RunnableImplDemo implements Runnable {// 1. 重写run()方法,定义任务逻辑@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("[" + Thread.currentThread().getName() + "] 执行任务,i=" + i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {// 2. 创建任务对象(Runnable实例)RunnableImplDemo task = new RunnableImplDemo();// 3. 创建线程对象,将任务传入Thread构造器(关键:线程与任务分离)Thread thread1 = new Thread(task, "线程C"); // 第二个参数直接设置线程名Thread thread2 = new Thread(task, "线程D");// 4. 启动线程thread1.start();thread2.start();System.out.println("[" + Thread.currentThread().getName() + "] 主线程执行完毕");}
}
执行结果(部分):
[main] 主线程执行完毕
[线程C] 执行任务,i=0
[线程D] 执行任务,i=0
[线程C] 执行任务,i=1
[线程D] 执行任务,i=1
4. 核心优势:任务复用与解耦
与“继承Thread”相比,Runnable的核心优势是任务可复用——同一个Runnable实例(任务)可以被多个Thread实例(线程)共享执行。
例如,若要实现“两个线程共同累加一个计数器”,用Runnable可轻松实现(任务共享计数器):
public class SharedTaskDemo implements Runnable {private int count = 0; // 共享计数器(线程安全需额外处理,此处仅演示复用)@Overridepublic void run() {for (int i = 0; i < 3; i++) {count++;System.out.println("[" + Thread.currentThread().getName() + "] count=" + count);}}public static void main(String[] args) {SharedTaskDemo sharedTask = new SharedTaskDemo();// 两个线程共享同一个任务对象,操作同一个countnew Thread(sharedTask, "线程E").start();new Thread(sharedTask, "线程F").start();}
}
执行结果(可能):
[线程E] count=1
[线程F] count=2
[线程E] count=3
[线程F] count=4
[线程E] count=5
[线程F] count=6
5. 与Thread类的对比
| 对比维度 | 继承Thread类 | 实现Runnable接口 |
|---|---|---|
| 继承限制 | 受单继承限制,无法再继承其他类 | 无继承限制,可同时实现其他接口 |
| 耦合度 | 线程与任务耦合(线程对象即任务) | 线程与任务解耦(任务独立,可复用) |
| 代码扩展性 | 差(任务逻辑无法单独抽离) | 好(任务可作为参数传递,便于模块化) |
| Java 8支持 | 可通过匿名内部类简化,但不如Runnable灵活 | 支持Lambda表达式(因Runnable是函数式接口) |
6. 优缺点分析
| 优点 | 缺点 |
|---|---|
| 无单继承限制,灵活性更高 | 无法直接获取线程对象:需通过Thread.currentThread()获取,而非this |
| 任务与线程解耦,支持任务复用 | 无返回值:run()方法无返回值,无法获取线程执行结果 |
| 支持Lambda表达式(Java 8+),代码更简洁 | 不抛checked异常:run()方法声明无异常抛出,需在方法内部捕获 |
三、实现Callable接口(带返回值的线程)
无论是Thread还是Runnable,都存在一个明显缺陷:无法获取线程执行的返回结果。为解决这个问题,Java 5引入了Callable接口——它与Runnable类似,但支持返回值和抛出checked异常。
不过,Callable不能直接传入Thread(因Thread仅接收Runnable),需借助FutureTask作为“桥梁”(FutureTask实现了Runnable接口)。
1. 原理剖析
(1)Callable接口定义
Callable是一个泛型接口,泛型参数代表返回值类型,核心方法为call():
@FunctionalInterface
public interface Callable<V> {V call() throws Exception; // 有返回值、可抛checked异常
}
(2)FutureTask的桥梁作用
FutureTask实现了RunnableFuture接口,而RunnableFuture继承了Runnable和Future:
public class FutureTask<V> implements RunnableFuture<V> {// 构造器:接收Callable实例public FutureTask(Callable<V> callable) { ... }// 实现Runnable的run()方法:内部会调用Callable的call(),并存储结果@Overridepublic void run() { ... }
}public interface RunnableFuture<V> extends Runnable, Future<V> { ... }
因此,FutureTask的核心作用是:
- 作为
Runnable,可传入Thread启动线程; - 作为
Future,可通过get()方法获取Callable的返回值、判断任务是否完成、取消任务。
(3)核心关系流程图
graph LRA[实现Callable接口<br>重写call()(带返回值)] -->|创建实例| B[Callable<V> 任务对象]B -->|传入构造器| C[FutureTask<V> 实例<br>(实现RunnableFuture)]C -->|作为Runnable传入| D[Thread 线程对象]D -->|调用start()| E[执行call()并存储结果]C -->|调用get()| F[获取返回值/抛出异常]C -->|调用isDone()| G[判断任务是否完成]
2. 实现步骤
- 定义自定义类,实现
Callable<V>接口(V为返回值类型); - 重写
call()方法,编写任务逻辑并返回结果(可抛异常); - 创建
Callable<V>实例(任务对象); - 创建
FutureTask<V>实例,将Callable实例传入; - 创建
Thread实例,将FutureTask实例传入; - 调用
Thread的start()方法启动线程; - 调用
FutureTask的get()方法获取call()的返回值(会阻塞当前线程,直到结果返回)。
3. 完整代码示例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** 方式3:实现Callable接口(带返回值)+ FutureTask*/
public class CallableImplDemo implements Callable<Integer> {private int start;private int end;// 构造器:传入计算范围(示例:计算start到end的累加和)public CallableImplDemo(int start, int end) {this.start = start;this.end = end;}// 1. 重写call()方法,带返回值、可抛异常@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = start; i <= end; i++) {sum += i;System.out.println("[" + Thread.currentThread().getName() + "] 正在计算:i=" + i + ",当前和=" + sum);Thread.sleep(50); // 模拟耗时}return sum; // 返回计算结果}public static void main(String[] args) {// 2. 创建Callable任务对象(计算1-10的和)Callable<Integer> callableTask = new CallableImplDemo(1, 10);// 3. 创建FutureTask实例(桥梁:Callable → Runnable)FutureTask<Integer> futureTask = new FutureTask<>(callableTask);// 4. 创建Thread并传入FutureTask,启动线程Thread thread = new Thread(futureTask, "计算线程");thread.start();// 5. 主线程逻辑:获取结果(会阻塞,直到子线程完成)try {// get():阻塞当前线程,直到call()执行完毕并返回结果Integer result = futureTask.get();System.out.println("\n[" + Thread.currentThread().getName() + "] 子线程计算结果:1-10的和=" + result);} catch (InterruptedException e) {// 线程被中断时抛出e.printStackTrace();} catch (ExecutionException e) {// call()方法抛出异常时,会被封装为ExecutionException抛出System.out.println("子线程执行异常:" + e.getCause().getMessage());}}
}
执行结果(部分):
[计算线程] 正在计算:i=1,当前和=1
[计算线程] 正在计算:i=2,当前和=3
...
[计算线程] 正在计算:i=10,当前和=55[main] 子线程计算结果:1-10的和=55
4. 关键注意点
get()方法的阻塞性:futureTask.get()会阻塞调用线程(如主线程),直到子线程的call()方法执行完毕。若需避免阻塞,可先通过futureTask.isDone()判断任务是否完成,再决定是否调用get()。- 异常处理:
call()方法抛出的异常会被封装为ExecutionException,需通过e.getCause()获取原始异常。 - 任务取消:可通过
futureTask.cancel(boolean mayInterruptIfRunning)取消任务:mayInterruptIfRunning=true:若任务已在执行,会中断线程;mayInterruptIfRunning=false:仅取消未开始的任务。
5. 优缺点分析
| 优点 | 缺点 |
|---|---|
| 支持返回值:可获取线程执行的结果 | get()方法会阻塞:若子线程执行时间长,会阻塞调用线程 |
| 支持抛异常:可将任务中的异常抛出到调用线程处理 | 代码复杂度高:需额外创建FutureTask实例,步骤比Runnable多 |
可取消任务:通过cancel()方法终止未完成的任务 | 无法直接复用任务:若多个线程需执行同一任务,需创建多个Callable实例 |
四、使用线程池(高并发场景必备)
无论是继承Thread、实现Runnable还是Callable,每次创建线程都会涉及“操作系统内核态与用户态的切换”,且线程执行完毕后会被销毁——频繁创建/销毁线程会带来巨大的性能开销。
为解决这个问题,Java提供了线程池(ExecutorService):线程池会预先创建一批线程,线程执行完任务后不会销毁,而是回到线程池等待下一个任务,从而实现线程的复用,降低性能开销。
1. 原理剖析
(1)线程池的核心组件
- 线程池管理器(ExecutorService):负责线程池的创建、管理和销毁,提供提交任务的接口(如
submit()、execute())。 - 工作线程(Worker):线程池中预先创建的线程,负责执行任务,执行完后回到线程池等待新任务。
- 任务队列(BlockingQueue):当核心线程都在忙时,新提交的任务会被放入任务队列暂存。
- 拒绝策略(RejectedExecutionHandler):当任务队列满且线程池达到最大线程数时,对新任务的处理策略(如抛出异常、丢弃任务等)。
(2)线程池工作流程


(3)核心参数解析
创建线程池的核心类是ThreadPoolExecutor,其构造器包含7个核心参数,决定了线程池的行为:
| 参数名称 | 类型 | 作用 | 示例 |
|---|---|---|---|
| corePoolSize | int | 核心线程数:线程池长期维持的线程数量(即使空闲也不销毁) | 核心线程数=CPU核心数+1 |
| maximumPoolSize | int | 最大线程数:线程池允许创建的最大线程数(核心线程数+非核心线程数) | 最大线程数=CPU核心数*2 |
| keepAliveTime | long | 非核心线程空闲时间:超过此时间,非核心线程会被销毁 | 60L(单位:秒) |
| unit | TimeUnit | keepAliveTime的时间单位 | TimeUnit.SECONDS |
| workQueue | BlockingQueue | 任务队列:暂存待执行任务的队列 | LinkedBlockingQueue(无界队列)、ArrayBlockingQueue(有界队列) |
| threadFactory | ThreadFactory | 线程工厂:用于创建线程(可自定义线程名称、优先级等) | Executors.defaultThreadFactory() |
| handler | RejectedExecutionHandler | 拒绝策略:任务队列满且线程数达最大时的处理策略 | AbortPolicy(默认:抛出异常) |
2. 线程池的创建方式
Java提供了两种创建线程池的方式:
- 通过
Executors工具类:快速创建预设参数的线程池(适合简单场景,不推荐高并发场景); - 直接创建
ThreadPoolExecutor实例:自定义核心参数(推荐,可避免Executors的潜在风险)。
(1)方式1:通过Executors工具类创建
Executors提供了4种常用的线程池工厂方法:
| 线程池类型 | 工厂方法 | 核心参数特点 | 适用场景 |
|---|---|---|---|
| FixedThreadPool | Executors.newFixedThreadPool(n) | 核心线程数=最大线程数=n,队列无界 | 任务数量固定、需长期执行的场景(如服务端处理请求) |
| CachedThreadPool | Executors.newCachedThreadPool() | 核心线程数=0,最大线程数=Integer.MAX_VALUE,队列同步移交 | 任务数量多、执行时间短的场景(如临时任务处理) |
| SingleThreadExecutor | Executors.newSingleThreadExecutor() | 核心线程数=1,最大线程数=1,队列无界 | 需串行执行任务的场景(如日志写入、单线程处理请求) |
| ScheduledThreadPool | Executors.newScheduledThreadPool(n) | 核心线程数=n,最大线程数=Integer.MAX_VALUE | 定时/周期性执行任务的场景(如定时备份、心跳检测) |
代码示例:FixedThreadPool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 方式4:通过Executors创建FixedThreadPool*/
public class ExecutorsFixedDemo {public static void main(String[] args) {// 1. 创建FixedThreadPool(核心线程数=2,最大线程数=2)ExecutorService executorService = Executors.newFixedThreadPool(2);// 2. 提交3个任务(核心线程数=2,第3个任务会进入队列等待)for (int i = 1; i <= 3; i++) {int taskId = i; // 匿名内部类引用外部变量需final或有效final// 提交Runnable任务(无返回值)executorService.execute(() -> {System.out.println("[" + Thread.currentThread().getName() + "] 执行任务" + taskId);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}});}// 3. 提交Callable任务(有返回值,需用submit()方法)executorService.submit(() -> {Thread.sleep(300);return "Callable任务执行完毕";}).thenAccept(result -> System.out.println("Callable任务结果:" + result)); // Java 8+ CompletableFuture特性// 4. 关闭线程池(重要:不关闭会导致JVM无法退出)// shutdown():等待所有已提交任务执行完毕后关闭线程池executorService.shutdown();// shutdownNow():立即关闭线程池,终止未执行的任务(慎用)// executorService.shutdownNow();}
}
执行结果(部分):
[pool-1-thread-1] 执行任务1
[pool-1-thread-2] 执行任务2
[pool-1-thread-1] 执行任务3
Callable任务结果:Callable任务执行完毕
(2)方式2:直接创建ThreadPoolExecutor(推荐)
Executors创建的线程池存在潜在风险(如FixedThreadPool的无界队列可能导致OOM),因此阿里巴巴《Java开发手册》推荐直接使用ThreadPoolExecutor自定义线程池,明确核心参数,避免资源耗尽。
代码示例:自定义线程池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** 方式4:直接创建ThreadPoolExecutor(推荐)*/
public class ThreadPoolExecutorDemo {public static void main(String[] args) {// 1. 定义核心参数int corePoolSize = 2; // 核心线程数=2int maximumPoolSize = 4; // 最大线程数=4long keepAliveTime = 60L; // 非核心线程空闲60秒后销毁TimeUnit unit = TimeUnit.SECONDS; // 时间单位:秒// 任务队列:有界队列,容量=3(超过3个任务会创建非核心线程)ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(3);// 拒绝策略:当队列满且线程数达最大时,抛出异常(默认策略)ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();// 2. 创建自定义线程池ExecutorService customExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,Executors.defaultThreadFactory(), // 默认线程工厂abortPolicy);// 3. 提交5个任务(核心2 + 队列3 = 5,无需创建非核心线程)for (int i = 1; i <= 5; i++) {int taskId = i;customExecutor.execute(() -> {System.out.println("[" + Thread.currentThread().getName() + "] 执行任务" + taskId);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}});}// 4. 提交第6个任务(队列满,创建第3个线程)customExecutor.execute(() -> {System.out.println("[" + Thread.currentThread().getName() + "] 执行任务6(非核心线程)");});// 5. 关闭线程池customExecutor.shutdown();}
}
3. 关键注意点
- 线程池的关闭:必须调用
shutdown()或shutdownNow()关闭线程池,否则线程池的核心线程会一直存活,导致JVM无法退出。shutdown():温和关闭,等待所有已提交任务执行完毕后关闭;shutdownNow():强制关闭,立即中断所有正在执行的任务,并返回未执行的任务列表。
- 拒绝策略的选择:
AbortPolicy(默认):抛出RejectedExecutionException,适合需要明确感知任务拒绝的场景;DiscardPolicy:直接丢弃任务,不抛出异常,适合非核心任务;DiscardOldestPolicy:丢弃队列中最旧的任务,然后提交新任务,适合任务有先后顺序的场景;CallerRunsPolicy:由提交任务的线程(如主线程)自己执行任务,适合需要避免任务丢失的场景。
- 线程池参数的调优:核心参数需根据业务场景调整,例如:
- CPU密集型任务(如计算):核心线程数=CPU核心数+1(减少线程切换开销);
- IO密集型任务(如网络请求、数据库操作):核心线程数=CPU核心数*2(利用IO等待时间复用线程)。
4. 优缺点分析
| 优点 | 缺点 |
|---|---|
| 线程复用:避免频繁创建/销毁线程,降低性能开销 | 配置复杂:需根据业务场景合理设置核心参数(如队列大小、最大线程数) |
| 控制并发:通过核心参数限制最大并发数,避免系统资源耗尽 | 任务堆积风险:若任务执行速度慢,队列可能堆积导致OOM(需用有界队列) |
| 管理便捷:提供统一的任务提交和线程管理接口 | 线程泄漏风险:若忘记关闭线程池,核心线程会一直存活,浪费资源 |
支持异步:可结合Callable获取任务结果,支持定时任务 | 调试难度高:多线程并发问题(如死锁、线程安全)排查复杂 |
五、CompletableFuture(Java 8+异步神器)
Callable+FutureTask虽然支持返回值,但存在一个痛点:获取结果时需要主动调用get()方法,会阻塞线程。为解决这个问题,Java 8引入了CompletableFuture——它基于FutureTask扩展,支持异步回调,无需阻塞即可处理线程执行结果,极大简化了异步编程。
1. 原理剖析
CompletableFuture实现了CompletionStage和Future接口:
CompletionStage:定义了异步任务的“阶段”,支持链式调用(如thenAccept()、thenApply()),一个阶段完成后自动触发下一个阶段;Future:继承了Future的核心能力(如get()、cancel())。
CompletableFuture默认使用ForkJoinPool.commonPool()(一个共享的线程池)执行任务,也可自定义线程池。
2. 核心方法分类
CompletableFuture的方法众多,按功能可分为“创建异步任务”和“处理任务结果”两类:
| 方法类型 | 核心方法 | 作用 |
|---|---|---|
| 创建异步任务(无返回值) | runAsync(Runnable runnable) | 使用默认线程池执行任务 |
runAsync(Runnable runnable, Executor executor) | 使用自定义线程池执行任务 | |
| 创建异步任务(有返回值) | supplyAsync(Supplier<U> supplier) | 使用默认线程池执行任务,返回结果 |
supplyAsync(Supplier<U> supplier, Executor executor) | 使用自定义线程池执行任务,返回结果 | |
| 处理任务结果(回调) | thenAccept(Consumer<? super U> action) | 任务完成后,消费结果(无返回值) |
thenApply(Function<? super U, ? extends V> fn) | 任务完成后,转换结果(有返回值,可链式调用) | |
exceptionally(Function<Throwable, ? extends U> fn) | 任务异常时,处理异常并返回默认值 |
3. 完整代码示例
(1)示例1:无返回值的异步任务
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 方式5:CompletableFuture(无返回值)*/
public class CompletableFutureRunAsyncDemo {public static void main(String[] args) {// 1. 创建自定义线程池(推荐,避免使用默认的ForkJoinPool)ExecutorService executor = Executors.newFixedThreadPool(2);// 2. 使用runAsync创建无返回值的异步任务CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {System.out.println("[" + Thread.currentThread().getName() + "] 执行异步任务(无返回值)");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}, executor); // 传入自定义线程池// 3. 任务完成后的回调(无需阻塞,自动执行)future.thenRun(() -> {System.out.println("[" + Thread.currentThread().getName() + "] 异步任务执行完毕(回调)");});// 4. 主线程继续执行其他逻辑(无阻塞)System.out.println("[" + Thread.currentThread().getName() + "] 主线程执行其他任务");// 5. 关闭线程池(注意:需等待异步任务完成,否则可能中断任务)executor.shutdown();}
}
执行结果:
[main] 主线程执行其他任务
[pool-1-thread-1] 执行异步任务(无返回值)
[pool-1-thread-1] 异步任务执行完毕(回调)
(2)示例2:有返回值的异步任务+链式回调
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 方式5:CompletableFuture(有返回值+链式回调)*/
public class CompletableFutureSupplyAsyncDemo {public static void main(String[] args) {ExecutorService executor = Executors.newSingleThreadExecutor();// 1. supplyAsync:创建有返回值的异步任务(返回String)CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {System.out.println("[" + Thread.currentThread().getName() + "] 执行异步任务(有返回值)");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException("任务被中断", e);}return "Hello, CompletableFuture!"; // 返回结果}, executor);// 2. thenApply:转换结果(String → Integer,返回新的CompletableFuture)CompletableFuture<Integer> lengthFuture = future.thenApply(result -> {System.out.println("[" + Thread.currentThread().getName() + "] 转换结果:原结果=" + result);return result.length(); // 将字符串转换为长度(Integer)});// 3. thenAccept:消费转换后的结果(无返回值)lengthFuture.thenAccept(length -> {System.out.println("[" + Thread.currentThread().getName() + "] 最终结果:字符串长度=" + length);});// 4. exceptionally:处理异常(若任务抛出异常,返回默认值)future.exceptionally(ex -> {System.out.println("任务执行异常:" + ex.getMessage());return "默认值"; // 异常时的返回值});executor.shutdown();}
}
执行结果:
[pool-1-thread-1] 执行异步任务(有返回值)
[pool-1-thread-1] 转换结果:原结果=Hello, CompletableFuture!
[pool-1-thread-1] 最终结果:字符串长度=23
4. 核心优势:非阻塞与链式编程
与传统的FutureTask相比,CompletableFuture的核心优势在于:
- 非阻塞回调:无需调用
get()阻塞线程,任务完成后自动触发回调方法; - 链式调用:支持多个回调阶段的链式组合(如
supplyAsync() → thenApply() → thenAccept()),代码更简洁; - 多任务组合:支持多个异步任务的协同(如
allOf():等待所有任务完成;anyOf():等待任意一个任务完成); - 自定义线程池:可避免默认
ForkJoinPool的资源竞争问题。
5. 优缺点分析
| 优点 | 缺点 |
|---|---|
| 非阻塞回调:无需阻塞线程,提高程序吞吐量 | 学习成本高:方法众多,需理解CompletionStage的阶段模型 |
| 链式编程:简化多步骤异步任务的代码逻辑 | 调试难度高:链式回调的异常堆栈可能不清晰 |
| 支持多任务组合:轻松实现复杂的异步协同逻辑 | 默认线程池风险:若使用默认ForkJoinPool,高并发下可能导致资源竞争 |
支持异常处理:通过exceptionally()统一处理异常 | 内存泄漏风险:若忘记关闭自定义线程池,会导致资源浪费 |
六、总结:如何选择合适的线程创建方式?
Java提供的6种线程创建方式,各有适用场景,选择的核心依据是业务需求(是否需要返回值、并发量、是否异步) 和性能要求。以下是各方式的适用场景汇总:
| 线程创建方式 | 核心特点 | 适用场景 |
|---|---|---|
| 继承Thread类 | 实现简单,单继承限制 | 简单的并发场景,无其他继承需求 |
| 实现Runnable接口 | 无继承限制,任务解耦 | 普通并发场景,需复用任务或多实现接口 |
| 实现Callable+FutureTask | 支持返回值和异常,需阻塞获取结果 | 需获取线程执行结果的场景(如计算任务) |
| 线程池(ExecutorService) | 线程复用,控制并发,高性能 | 高并发场景(如服务端处理请求、批量任务) |
| CompletableFuture | 非阻塞回调,链式编程,异步协同 | Java 8+的异步场景(如微服务调用、IO密集型任务) |
关键建议
- 避免频繁创建独立线程:除非是简单的一次性任务,否则优先使用线程池或
CompletableFuture,避免线程创建/销毁的性能开销; - 高并发场景首选线程池:通过自定义
ThreadPoolExecutor明确核心参数,避免Executors的潜在风险; - 异步回调用CompletableFuture:Java 8及以上版本,若需异步处理结果,优先使用
CompletableFuture,简化代码并提高吞吐量; - 线程安全是前提:无论选择哪种方式,都需注意线程安全(如使用
synchronized、ConcurrentHashMap等),避免数据竞争问题。
通过本文的详细拆解,相信你已掌握Java线程创建的所有方式。在实际开发中,需结合业务场景灵活选择,才能写出高效、稳定的并发代码。
