跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java 并发编程:6 种线程创建方式及线程池使用指南

Java 并发编程中线程创建的六种核心方式。包括继承 Thread 类、实现 Runnable 接口、实现 Callable 接口配合 FutureTask、使用线程池(Executors 及 ThreadPoolExecutor)、以及 Java 8 的 CompletableFuture。文章详细解析了各方式的原理、代码示例、优缺点对比及适用场景,重点强调了线程池参数配置、拒绝策略及异步回调的最佳实践,帮助开发者根据业务需求选择合适的并发方案。

CryptoLab发布于 2026/3/24更新于 2026/7/1051 浏览
Java 并发编程:6 种线程创建方式及线程池使用指南

引言:线程与 Java 并发的核心

在 Java 中,线程是实现并发编程的基础单元,它允许程序在同一时间执行多个任务(如后台处理、异步通信等)。Java 提供了多种创建线程的方式,每种方式都有其设计初衷、适用场景和优缺点。本文将详细拆解 Java 中创建线程的 6 种核心方式,包括原理剖析、代码实战、注意事项,并通过流程图辅助理解,帮助你彻底掌握线程创建的底层逻辑与实践技巧。

一、继承 Thread 类(最基础的线程创建方式)

Thread是 Java 中封装线程操作的核心类,它本身实现了 Runnable接口。通过继承 Thread 类并重写 run() 方法,可以定义线程的执行逻辑,这是最基础的线程创建方式。

1. 原理剖析
  • Thread类的核心作用:封装了线程的生命周期(新建、就绪、运行、阻塞、终止)和底层操作系统调用(如启动线程、中断线程)。
  • 线程执行逻辑的载体:run()方法是线程的'任务入口',当线程启动后,JVM 会自动调用该方法执行任务;若未重写 run(),则会执行父类 Thread的默认实现(无实际逻辑)。
2. 实现步骤
  1. 定义自定义类,继承 Thread类;
  2. 重写 Thread类的 run()方法,在方法体内编写线程要执行的任务逻辑;
  3. 创建自定义类的实例(即线程对象);
  4. 调用线程对象的 start()方法,启动线程(注意:不可直接调用 run() 方法)。
3. 完整代码示例
/**
 * 方式 1:继承 Thread 类创建线程
 */
public class ThreadExtendDemo extends Thread {
    // 1. 重写 run() 方法,定义线程任务
    @Override
    public 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 TD
A[调用 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. 实现步骤
  1. 定义自定义类,实现 Runnable接口;
  2. 重写 Runnable的 run()方法,编写任务逻辑;
  3. 创建 Runnable实例(任务对象);
  4. 创建 Thread实例,将 Runnable实例传入 Thread构造器;
  5. 调用 Thread实例的 start()方法启动线程。
3. 完整代码示例
/**
 * 方式 2:实现 Runnable 接口创建线程
 */
public class RunnableImplDemo implements Runnable {
    // 1. 重写 run() 方法,定义任务逻辑
    @Override
    public 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; // 共享计数器(线程安全需额外处理,此处仅演示复用)

    @Override
    public 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();
        // 两个线程共享同一个任务对象,操作同一个 count
        new 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(),并存储结果
    @Override
    public void run() { ... }
}
public interface RunnableFuture<V> extends Runnable, Future<V> { ... }

因此,FutureTask的核心作用是:

  • 作为 Runnable,可传入 Thread启动线程;
  • 作为 Future,可通过 get()方法获取 Callable的返回值、判断任务是否完成、取消任务。
(3)核心关系流程图
graph LR
A[实现 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. 实现步骤
  1. 定义自定义类,实现 Callable<V>接口(V 为返回值类型);
  2. 重写 call()方法,编写任务逻辑并返回结果(可抛异常);
  3. 创建 Callable<V>实例(任务对象);
  4. 创建 FutureTask<V>实例,将 Callable实例传入;
  5. 创建 Thread实例,将 FutureTask实例传入;
  6. 调用 Thread的 start()方法启动线程;
  7. 调用 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() 方法,带返回值、可抛异常
    @Override
    public 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 个核心参数,决定了线程池的行为:

参数名称类型作用示例
corePoolSizeint核心线程数:线程池长期维持的线程数量(即使空闲也不销毁)核心线程数=CPU 核心数 +1
maximumPoolSizeint最大线程数:线程池允许创建的最大线程数(核心线程数 + 非核心线程数)最大线程数=CPU 核心数*2
keepAliveTimelong非核心线程空闲时间:超过此时间,非核心线程会被销毁60L(单位:秒)
unitTimeUnitkeepAliveTime 的时间单位TimeUnit.SECONDS
workQueueBlockingQueue任务队列:暂存待执行任务的队列LinkedBlockingQueue(无界队列)、ArrayBlockingQueue(有界队列)
threadFactoryThreadFactory线程工厂:用于创建线程(可自定义线程名称、优先级等)Executors.defaultThreadFactory()
handlerRejectedExecutionHandler拒绝策略:任务队列满且线程数达最大时的处理策略AbortPolicy(默认:抛出异常)
2. 线程池的创建方式

Java 提供了两种创建线程池的方式:

  1. 通过Executors工具类:快速创建预设参数的线程池(适合简单场景,不推荐高并发场景);
  2. 直接创建ThreadPoolExecutor实例:自定义核心参数(推荐,可避免 Executors的潜在风险)。
(1)方式 1:通过 Executors 工具类创建

Executors提供了 4 种常用的线程池工厂方法:

线程池类型工厂方法核心参数特点适用场景
FixedThreadPoolExecutors.newFixedThreadPool(n)核心线程数=最大线程数=n,队列无界任务数量固定、需长期执行的场景(如服务端处理请求)
CachedThreadPoolExecutors.newCachedThreadPool()核心线程数=0,最大线程数=Integer.MAX_VALUE,队列同步移交任务数量多、执行时间短的场景(如临时任务处理)
SingleThreadExecutorExecutors.newSingleThreadExecutor()核心线程数=1,最大线程数=1,队列无界需串行执行任务的场景(如日志写入、单线程处理请求)
ScheduledThreadPoolExecutors.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; // 核心线程数=2
        int maximumPoolSize = 4; // 最大线程数=4
        long 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的核心优势在于:

  1. 非阻塞回调:无需调用 get()阻塞线程,任务完成后自动触发回调方法;
  2. 链式调用:支持多个回调阶段的链式组合(如 supplyAsync() → thenApply() → thenAccept()),代码更简洁;
  3. 多任务组合:支持多个异步任务的协同(如 allOf():等待所有任务完成;anyOf():等待任意一个任务完成);
  4. 自定义线程池:可避免默认 ForkJoinPool的资源竞争问题。
5. 优缺点分析
优点缺点
非阻塞回调:无需阻塞线程,提高程序吞吐量学习成本高:方法众多,需理解 CompletionStage的阶段模型
链式编程:简化多步骤异步任务的代码逻辑调试难度高:链式回调的异常堆栈可能不清晰
支持多任务组合:轻松实现复杂的异步协同逻辑默认线程池风险:若使用默认 ForkJoinPool,高并发下可能导致资源竞争
支持异常处理:通过 exceptionally()统一处理异常内存泄漏风险:若忘记关闭自定义线程池,会导致资源浪费

六、总结:如何选择合适的线程创建方式?

Java 提供的 6 种线程创建方式,各有适用场景,选择的核心依据是业务需求(是否需要返回值、并发量、是否异步) 和性能要求。以下是各方式的适用场景汇总:

线程创建方式核心特点适用场景
继承 Thread 类实现简单,单继承限制简单的并发场景,无其他继承需求
实现 Runnable 接口无继承限制,任务解耦普通并发场景,需复用任务或多实现接口
实现 Callable+FutureTask支持返回值和异常,需阻塞获取结果需获取线程执行结果的场景(如计算任务)
线程池(ExecutorService)线程复用,控制并发,高性能高并发场景(如服务端处理请求、批量任务)
CompletableFuture非阻塞回调,链式编程,异步协同Java 8+的异步场景(如微服务调用、IO 密集型任务)
关键建议
  1. 避免频繁创建独立线程:除非是简单的一次性任务,否则优先使用线程池或 CompletableFuture,避免线程创建/销毁的性能开销;
  2. 高并发场景首选线程池:通过自定义 ThreadPoolExecutor明确核心参数,避免 Executors的潜在风险;
  3. 异步回调用 CompletableFuture:Java 8 及以上版本,若需异步处理结果,优先使用 CompletableFuture,简化代码并提高吞吐量;
  4. 线程安全是前提:无论选择哪种方式,都需注意线程安全(如使用 synchronized、ConcurrentHashMap等),避免数据竞争问题。

通过本文的详细拆解,相信你已掌握 Java 线程创建的所有方式。在实际开发中,需结合业务场景灵活选择,才能写出高效、稳定的并发代码。

目录

  1. 引言:线程与 Java 并发的核心
  2. 一、继承 Thread 类(最基础的线程创建方式)
  3. 1. 原理剖析
  4. 2. 实现步骤
  5. 3. 完整代码示例
  6. 注意:输出顺序是由每个线程自己抢的,不是固定的。
  7. 4. 关键注意点:start() vs run()
  8. 5. 优缺点分析
  9. 二、实现 Runnable 接口(解耦首选方式)
  10. 1. 原理剖析
  11. 2. 实现步骤
  12. 3. 完整代码示例
  13. 4. 核心优势:任务复用与解耦
  14. 5. 与 Thread 类的对比
  15. 6. 优缺点分析
  16. 三、实现 Callable 接口(带返回值的线程)
  17. 1. 原理剖析
  18. (1)Callable 接口定义
  19. (2)FutureTask 的桥梁作用
  20. (3)核心关系流程图
  21. 2. 实现步骤
  22. 3. 完整代码示例
  23. 4. 关键注意点
  24. 5. 优缺点分析
  25. 四、使用线程池(高并发场景必备)
  26. 1. 原理剖析
  27. (1)线程池的核心组件
  28. (2)线程池工作流程
  29. (3)核心参数解析
  30. 2. 线程池的创建方式
  31. (1)方式 1:通过 Executors 工具类创建
  32. (2)方式 2:直接创建 ThreadPoolExecutor(推荐)
  33. 3. 关键注意点
  34. 4. 优缺点分析
  35. 五、CompletableFuture(Java 8+异步神器)
  36. 1. 原理剖析
  37. 2. 核心方法分类
  38. 3. 完整代码示例
  39. (1)示例 1:无返回值的异步任务
  40. (2)示例 2:有返回值的异步任务 + 链式回调
  41. 4. 核心优势:非阻塞与链式编程
  42. 5. 优缺点分析
  43. 六、总结:如何选择合适的线程创建方式?
  44. 关键建议
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • LeetCode 206 反转链表:思路解析与 Java 代码实现
  • LM358 在智能家居中的 5 个实用电路案例
  • SAP 对接钉钉 Webhook 实现方案及证书问题排查
  • Neo4j Windows 环境搭建与安装指南
  • Retinaface+CurricularFace 基于 Kubernetes StatefulSet 部署方案
  • VSCode Copilot 代码补全准确率优化实战指南
  • 深入理解 MySQL 索引:核心原理与实战优化指南
  • C++高性能游戏渲染优化实践:减少 CPU-GPU 等待时间的 4 种方法
  • GitHub 2FA 双重验证实战指南:Edge 插件配置方案
  • C++26 CPU 亲和性底层机制与性能优化实践
  • Python aes-python 包语法、参数与实战案例
  • Java 核心工具类实战:String 与日期时间 API
  • PyQt5 基础与常用控件入门教程
  • 基于低代码引擎快速构建企业级审批系统实战
  • AI 辅助下的多链 imToken 钱包架构设计与安全实践
  • AIGC 检测技术:如何识别 AI 生成内容并保障原创性
  • Stable Diffusion 3.5 云端部署与图像生成实测
  • 后量子密码算法(PQC):NIST 标准、核心算法与落地趋势
  • 字节跳动开源 Seed-OSS-36B:512K 上下文推理新突破
  • 人工智能应用技术核心内容与学习路径

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online