欢迎光临
我们一直在努力

Java线程解析:5种线程创建方法及应用场景

一、前言

上一篇文章我们介绍了 Java 多线程的基础概念,并且演示了两种创建线程的基础方式:继承 Thread 类和实现 Runnable 接口。但在实际开发中,这两种方式并不能满足所有场景需求,比如需要获取线程执行结果、需要统一管理线程生命周期等。

本文将全面讲解 Java 中创建线程的5种方式,分析它们的优缺点和适用场景,并通过代码案例帮助大家掌握实际用法。

 

 

二、5 种线程创建方式

方式 1:继承 Thread 类

这是最基础的线程创建方式,核心是继承 Thread 类并重写 run() 方法,通过 start() 方法启动线程。

实现步骤

  • 定义一个类继承 Thread 类;

  • 重写 run() 方法,编写线程执行逻辑;

  • 创建该类的实例对象,调用 start() 方法启动线程。

  • 代码案例

    /**
     * 继承Thread类创建线程
     */
    class MyThread extends Thread {
        // 可以通过构造方法传递线程名称
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            // 线程执行的任务:打印当前线程名称和1-5的数字
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + " 执行:" + i);
                try {
                    // 休眠500毫秒,模拟耗时操作
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 测试类
    public class ThreadExtendDemo {
        public static void main(String[] args) {
            // 创建线程对象,指定线程名称
            MyThread thread1 = new MyThread("线程A");
            MyThread thread2 = new MyThread("线程B");
            // 启动线程
            thread1.start();
            thread2.start();
        }
    }

    优缺点分析

    • 优点:实现简单,直接继承 Thread 类即可,通过 this 就能获取当前线程对象。

    • 缺点:

  • Java 是单继承机制,继承 Thread 类后无法再继承其他类,扩展性差。

  • run() 方法没有返回值,无法获取线程执行结果。

  • 线程与任务耦合度高,一个线程类只能对应一个任务。

  •  

    方式 2:实现 Runnable 接口

    为了解决单继承的问题,Java 提供了 Runnable 接口,通过实现该接口并重写 run() 方法来定义任务,再将任务对象传入 Thread 类的构造方法创建线程。

    实现步骤

  • 定义一个类实现 Runnable 接口;

  • 实现 run()方法,编写线程执行逻辑;

  • 创建 Runnable 实现类的实例,作为参数传入 Thread 构造方法;

  • 调用 Thread 实例的 start()方法启动线程。

  • 代码案例

    /**
     * 实现Runnable接口创建线程(定义任务)
     */
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + " 执行:" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 测试类
    public class RunnableImplDemo {
        public static void main(String[] args) {
            // 创建任务对象
            MyRunnable task = new MyRunnable();
            // 创建线程对象,传入任务并指定线程名称
            Thread thread1 = new Thread(task, "线程C");
            Thread thread2 = new Thread(task, "线程D");
            // 启动线程
            thread1.start();
            thread2.start();
        }
    }

    优缺点分析

    • 优点:

  • 避免了单继承的限制,实现 Runnable 接口的同时还能继承其他类。

  • 线程与任务解耦,一个任务对象可以被多个线程共享。

  • 适合多个线程执行相同任务的场景。

    • 缺点: run() 方法仍然没有返回值,无法获取线程执行结果;无法直接抛出受检异常。

     

    方式 3:实现 Callable 接口 + FutureTask

    在实际开发中,我们经常需要获取线程的执行结果(比如异步计算任务), Callable 接口正是为了解决这个问题而生的。 Callable 接口的 call() 方法可以返回结果,并且支持抛出异常,需要配合 FutureTask 类来使用。

    实现步骤

  • 定义一个类实现 Callable<T> 接口(T 为返回值类型);

  • 实现 call() 方法,编写带返回值的线程逻辑;

  • 创建 Callable 实现类实例,传入 FutureTask 构造方法;

  • 将 FutureTask 实例传入 Thread 构造方法,调用 start() 启动线程;

  • 通过 FutureTask 的 get() 方法获取线程执行结果(会阻塞直到结果返回)。

  • 代码案例

    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    /**
     * 实现Callable接口创建线程(支持返回值)
     */
    class MyCallable implements Callable<Integer> {
        // 可以通过构造方法传递参数
        private int start;
        private int end;
        public MyCallable(int start, int end) {
            this.start = start;
            this.end = end;
        }
        @Override
        public Integer call() throws Exception {
            // 任务:计算start到end的累加和
            int sum = 0;
            for (int i = start; i <= end; i++) {
                sum += i;
                System.out.println(Thread.currentThread().getName() + " 计算中:i=" + i + ",当前和=" + sum);
                Thread.sleep(300);
            }
            return sum;
        }
    }
    // 测试类
    public class CallableImplDemo {
        public static void main(String[] args) throws Exception {
            // 1. 创建Callable任务对象
            Callable<Integer> task1 = new MyCallable(1, 5);
            Callable<Integer> task2 = new MyCallable(6, 10);
            // 2. 创建FutureTask对象,包装Callable任务(FutureTask实现了RunnableFuture接口,可作为Thread的参数)
            FutureTask<Integer> futureTask1 = new FutureTask<>(task1);
            FutureTask<Integer> futureTask2 = new FutureTask<>(task2);
            // 3. 创建线程对象并启动
            new Thread(futureTask1, "计算线程1").start();
            new Thread(futureTask2, "计算线程2").start();
            // 4. 获取线程执行结果(get()方法会阻塞,直到线程执行完成)
            Integer sum1 = futureTask1.get();
            Integer sum2 = futureTask2.get();
            System.out.println("1-5的累加和:" + sum1);
            System.out.println("6-10的累加和:" + sum2);
            System.out.println("总和:" + (sum1 + sum2));
        }
    }

    核心说明

  • Callable 是一个泛型接口, call() 方法的返回值类型为 V 。

  • FutureTask 实现了 Runnable 和 Future 接口,既可以作为任务传入 Thread ,又可以通过 get() 方法获取任务执行结果。

  • get() 方法是阻塞方法,如果线程还未执行完成,调用 get() 的线程会等待,直到目标线程执行完毕返回结果。

  • 优缺点分析

    • 优点:

  • 支持获取线程执行结果,满足异步计算场景需求。

  • call() 方法可以抛出受检异常,便于异常处理。

  • 同样避免了单继承的限制。

    • 缺点:

  • 实现步骤相对复杂,需要配合 FutureTask 使用。

  • get() 方法会阻塞主线程,若结果获取时机不当,会影响程序执行效率。

  •  

    方式 4:使用线程池创建线程

    前面三种方式都是直接创建线程对象,在高并发场景下,频繁创建和销毁线程会带来巨大的性能开销。线程池可以复用线程,控制并发线程数量,降低系统资源消耗,是实际开发中最推荐的线程创建方式。Java 通过 java.util.concurrent.Executors 工具类或 ThreadPoolExecutor 来创建线程池。

    实现步骤

  • 通过 Executors 工具类或 ThreadPoolExecutor 手动创建线程池;

  • 将 Runnable 或 Callable 任务提交到线程池;

  • 线程池自动分配线程执行任务;

  • 任务执行完成后,关闭线程池(避免资源泄漏)。

  • 代码案例 1:使用 Executors 工具类创建线程池(适合简单场景)

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    /**
     * 使用线程池创建线程(Executors工具类)
     */
    public class ThreadPoolDemo1 {
        public static void main(String[] args) {
            // 1. 创建固定大小的线程池,核心线程数=最大线程数=3
            ExecutorService threadPool = Executors.newFixedThreadPool(3);
            // 2. 提交任务(可以提交Runnable或Callable任务)
            for (int i = 1; i <= 5; i++) {
                int taskId = i;
                // 提交Runnable任务
                threadPool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + " 执行任务" + taskId);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
            // 3. 关闭线程池(核心:先shutdown(),再awaitTermination()确保任务执行完毕)
            threadPool.shutdown();
        }
    }

     

    代码案例 2:使用 ThreadPoolExecutor 自定义线程池(适合复杂场景)

    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    /**
     * 自定义线程池(ThreadPoolExecutor)
     */
    public class ThreadPoolDemo2 {
        public static void main(String[] args) {
            // 1. 配置线程池参数
            int corePoolSize = 2; // 核心线程数
            int maximumPoolSize = 4; // 最大线程数
            long keepAliveTime = 60; // 非核心线程空闲存活时间
            TimeUnit unit = TimeUnit.SECONDS; // 时间单位
            // 任务队列:容量为2的有界队列
            ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
            // 2. 创建自定义线程池
            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    corePoolSize,
                    maximumPoolSize,
                    keepAliveTime,
                    unit,
                    workQueue
            );
            // 3. 提交6个任务(核心2 + 队列2 + 非核心2 = 刚好处理6个任务)
            for (int i = 1; i <= 6; i++) {
                int taskId = i;
                threadPool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + " 执行任务" + taskId);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
            // 4. 关闭线程池
            threadPool.shutdown();
        }
    }

    核心说明

  • 线程池的核心参数包括:核心线程数、最大线程数、空闲存活时间、任务队列、拒绝策略等。

  • Executors 工具类提供了多种线程池创建方法( newFixedThreadPool 、 newCachedThreadPool 、 newSingleThreadExecutor ),但在生产环境中,更推荐使用 ThreadPoolExecutor 自定义线程池,避免资源耗尽风险。

  • 线程池会复用线程执行任务,当任务数超过线程池处理能力时,任务会被放入队列等待。

  • 优缺点分析

    • 优点:

  • 线程复用,降低线程创建和销毁的性能开销。

  • 控制最大并发数,避免大量线程抢占资源导致系统瘫痪。

  • 提供任务队列、拒绝策略等功能,便于任务管理和异常处理。

  • 支持提交 Runnable 和 Callable 任务,灵活适配不同场景。

    • 缺点:线程池参数配置需要根据业务场景调优,参数配置不当会影响性能。

     

    方式 5:使用 CompletableFuture(Java 8+)

    Java 8 引入的 CompletableFuture 是对 Future 的增强,不仅能创建异步任务,还支持任务编排(串行、并行、聚合),无需手动管理线程,底层默认使用 ForkJoinPool.commonPool() 线程池,也可自定义线程池。

    实现步骤

  • 使用 CompletableFuture.supplyAsync()(有返回值)或 runAsync()(无返回值)创建异步任务;

  • (可选)传入自定义线程池,避免使用默认线程池;

  • 调用 join() 或 get() 获取结果(join() 不抛出检查异常,更友好);

  • (可选)通过 thenApply()、thenAccept() 等方法编排任务。

  • 代码示例

    示例 1:基础使用(无返回值 + 有返回值)

    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    public class CompletableFutureDemo {
        public static void main(String[] args) {
            // 1. 无返回值的异步任务(runAsync)
            CompletableFuture<Void> voidFuture = CompletableFuture.runAsync(() -> {
                System.out.println("线程 " + Thread.currentThread().getName() + " 执行无返回值任务(CompletableFuture)");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            // 等待任务完成
            voidFuture.join();
            // 2. 有返回值的异步任务(supplyAsync),使用自定义线程池
            ExecutorService customPool = Executors.newFixedThreadPool(2);
            CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
                System.out.println("线程 " + Thread.currentThread().getName() + " 执行有返回值任务(CompletableFuture)");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "异步任务执行完成";
            }, customPool);
            // 获取返回结果(join() 不抛出检查异常)
            String result = supplyFuture.join();
            System.out.println("有返回值任务结果:" + result);
            // 3. 任务编排示例:串行执行
            CompletableFuture<String> thenFuture = supplyFuture.thenApply(res -> {
                System.out.println("线程 " + Thread.currentThread().getName() + " 处理上一个任务的结果");
                return res + ",并完成后续处理";
            });
            System.out.println("编排任务结果:" + thenFuture.join());
            // 关闭自定义线程池
            customPool.shutdown();
        }
    }

     

    示例 2:异常处理

    import java.util.concurrent.CompletableFuture;
    public class CompletableFutureExceptionDemo {
        public static void main(String[] args) {
            CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
                // 模拟异常
                int a = 1 / 0;
                return 100;
            }).exceptionally(ex -> {
                // 捕获异常并返回默认值
                System.out.println("任务执行异常:" + ex.getMessage());
                return -1;
            });
            System.out.println("异常处理后结果:" + future.join());
        }
    }

     

    示例3:进阶使用(自定义线程池 + 链式调用)

    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    /**
     * CompletableFuture 进阶使用(自定义线程池 + 链式调用)
     */
    public class CompletableFutureDemo2 {
        public static void main(String[] args) {
            // 1. 创建自定义线程池
            ExecutorService customThreadPool = Executors.newFixedThreadPool(2);
            // 2. 异步任务1:计算1-5的和
            CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
                int sum = 0;
                for (int i = 1; i <= 5; i++) {
                    sum += i;
                    System.out.println(Thread.currentThread().getName() + " 计算1-5的和:" + sum);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return sum;
            }, customThreadPool);
            // 3. 链式调用:任务1完成后,异步处理其结果(乘以2)
            CompletableFuture<Integer> future2 = future1.thenApplyAsync(result -> {
                System.out.println(Thread.currentThread().getName() + " 处理任务1结果:" + result + " * 2 = " + (result * 2));
                return result * 2;
            }, customThreadPool);
            // 4. 最终处理:任务2完成后,消费结果(无返回值)
            future2.thenAccept(result -> {
                System.out.println("最终结果:" + result);
            });
            // 5. 关闭线程池(需等待所有任务完成)
            customThreadPool.shutdown();
        }
    }

    核心说明

    CompletableFuture 本身不创建线程,而是依赖线程池执行异步任务:

    • 默认情况下,runAsync()/supplyAsync() 会使用 ForkJoinPool.commonPool()(JDK 内置的通用线程池)执行任务;

    • 也可通过重载方法(如 supplyAsync(Supplier, Executor))指定自定义线程池,避免全局线程池资源竞争。

    优缺点分析

    • 优点:

  • 无需手动管理线程和 FutureTask,API 更简洁优雅。

  • 支持 thenApply、thenCombine、whenComplete 等链式调用,轻松实现多异步任务的串行、并行、聚合。

  • 提供 exceptionally、handle 等方法,可优雅处理异步任务中的异常,避免主线程阻塞崩溃。

  • 可指定任务执行的线程池,避免默认 ForkJoinPool 资源竞争。

  • 除了 get(),还可通过回调函数(如 thenAccept)异步处理结果,避免主线程阻塞。

  • 缺点:

  • API 丰富且抽象,新手需要理解异步回调、线程池适配等概念。

  • 异步链式调用的执行流程分散,调试时难以追踪完整调用链路。

  • ForkJoinPool.commonPool() 是全局共享的,大量任务提交可能导致资源耗尽。

  •  

    三、线程创建方式对比表

    方式

    核心类 / 接口

    是否有返回值

    是否支持继承其他类

    适用场景

    继承Thread类

    Thread

    ❌ 无

    ❌ 单继承限制

    简单线程任务,不需要复用线程

    实现Runnable接口

    Runnable

    Thread

    ❌ 无

    ✅ 支持

    多个线程执行相同任务,需要继承其他类

    实现Callable接口+FutureTask

    Callable

    FutureTask

    Thread

    ✅ 有

    ✅ 支持

    需要获取线程执行结果的异步计算场景

    使用线程池

    ExecutorService

    ThreadPoolExecutor

    ✅ 支持(提交 Callable 任务)

    ✅ 支持

    高并发场景,需要统一管理线程生命周期

    使用 CompletableFuture(Java8+)

    CompletableFuture

    ExecutorService

    ✅ 支持

    ✅ 支持

    现代异步编程,多任务组合、非阻塞结果处理

     

    四、核心注意事项

  • 启动线程必须调用start()方法:直接调用 run() 方法会以普通方法的形式串行执行,不会启动新线程。

  • 线程池使用后必须关闭:若不关闭线程池,核心线程会一直存活,导致程序无法正常退出。

  • FutureTask的get()方法会阻塞:建议在所有线程提交后再调用 get() 方法,或使用 get(long timeout, TimeUnit unit) 设置超时时间,避免主线程长时间阻塞。

  • 生产环境慎用Executors工具类: Executors 创建的线程池可能存在资源耗尽风险(如 newCachedThreadPool 最大线程数为 Integer.MAX_VALUE ),推荐使用 ThreadPoolExecutor 自定义参数。

  •  

    五、总结

    本文详细讲解了 Java 中创建线程的 5 种方式,从基础的继承 Thread 类、实现 Runnable 接口,到支持返回值的 Callable 接口,再到高并发场景下的线程池,以及支持异步回调与链式编程的 CompletableFuture,每种方式都有其适用场景。

    在实际开发中,线程池结合 CompletableFuture 是更优的推荐方案,而纯线程池也仍是核心优选,它们能有效提升程序性能和稳定性;其中 CompletableFuture 更能解决传统异步编程中回调嵌套的问题,进一步简化异步任务的编排与结果处理。

    下一篇文章,我们将深入讲解 Java 线程的生命周期与状态转换,通过代码演示不同状态之间的转换路径,帮助大家理解线程状态的本质。

     

    赞(0)
    未经允许不得转载:171主机测评 » Java线程解析:5种线程创建方法及应用场景
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址