java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java创建线程方式

Java中创建线程的六种方式详解

作者:青春逝如流水

在 Java 中,线程是并发编程的基本执行单元,创建一个线程看似简单,但 Java 发展近三十年来,围绕如何创建线程这一问题,提供了多种不同层次、不同风格的 API,本文系统梳理 Java 中创建线程的各种方式,涵盖其 API 用法、底层原理、适用场景以及相互之间的差异

引言

在 Java 中,线程是并发编程的基本执行单元。创建一个线程看似简单,但 Java 发展近三十年来,围绕“如何创建线程”这一问题,提供了多种不同层次、不同风格的 API。从最原始的继承 Thread 类,到 JDK 5 的线程池、JDK 8 的 CompletableFuture,再到 JDK 21 的虚拟线程,每一种方式都对应着特定的使用场景和设计哲学。

本文系统梳理 Java 中创建线程的各种方式,涵盖其 API 用法、底层原理、适用场景以及相互之间的差异。

一、继承 Thread 类

1.1 基本用法

最基础的方式是继承 java.lang.Thread 类,重写 run() 方法:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程运行中:" + Thread.currentThread().getName());
    }
}

// 使用
MyThread t = new MyThread();
t.start();

1.2 原理

Thread 类本身实现了 Runnable 接口。调用 start() 方法时,JVM 会通过本地方法 start0() 创建一个操作系统线程,新线程随后执行 run() 方法。注意:直接调用 run() 只是在当前线程中执行方法体,不会创建新线程。

1.3 局限

由于 Java 不支持多继承,继承 Thread 后无法再继承其他类。此外,任务逻辑与线程机制耦合在一起,不利于复用。因此这种方式通常只用于简单的演示或测试。

二、实现 Runnable 接口

2.1 基本用法

实现 java.lang.Runnable 接口,将任务逻辑与线程机制分离:

public class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("任务执行中:" + Thread.currentThread().getName());
    }
}

// 使用
Thread t = new Thread(new MyTask());
t.start();

2.2 Lambda 简化

由于 Runnable 是函数式接口,JDK 8 之后可以用 Lambda 表达式简化:

Thread t = new Thread(() -> System.out.println("运行中"));
t.start();

2.3 优势

任务与线程解耦,一个 Runnable 实例可以被多个线程共享执行,也可以配合线程池使用。这是最推荐的“裸线程”创建方式。

三、实现 Callable 接口 + FutureTask

3.1 基本用法

Runnable 的 run() 方法没有返回值,也无法抛出受检异常。java.util.concurrent.Callable 解决了这两个问题:

Callable<String> task = () -> {
    Thread.sleep(1000);
    return "执行结果";
};

FutureTask<String> futureTask = new FutureTask<>(task);
Thread t = new Thread(futureTask);
t.start();

// 获取返回值(阻塞直到完成)
String result = futureTask.get();

3.2 原理

FutureTask 同时实现了 Runnable 和 Future 接口。它包装 Callable,在 run() 中调用 call() 并将结果保存,get() 方法用于获取结果或捕获异常。

3.3 适用场景

需要获取任务执行结果或需要处理受检异常时,使用 Callable + FutureTask。但在实际工程中,通常直接配合线程池使用,而非手动创建 Thread。

四、使用 ExecutorService 线程池

4.1 基本用法

JDK 5 引入的 ExecutorService 是创建和管理线程的推荐方式:

ExecutorService executor = Executors.newFixedThreadPool(4);

Future<String> future = executor.submit(() -> {
    return "任务结果";
});

String result = future.get();
executor.shutdown();

4.2 常用工厂方法

Executors 工具类提供了多种线程池:

方法说明
newFixedThreadPool(n)固定大小线程池
newCachedThreadPool()可缓存线程池,空闲线程回收,需求时新建
newSingleThreadExecutor()单线程池,保证任务顺序执行
newScheduledThreadPool(n)支持定时和周期性任务
newWorkStealingPool()基于 Fork/Join 的工作窃取线程池

4.3 手动创建 ThreadPoolExecutor

Executors 的工厂方法在大型应用中存在资源耗尽风险,更推荐手动构造 ThreadPoolExecutor,明确指定核心线程数、最大线程数、队列容量和拒绝策略:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4,                          // 核心线程数
    8,                          // 最大线程数
    60L, TimeUnit.SECONDS,      // 空闲线程存活时间
    new ArrayBlockingQueue<>(100), // 有界队列
    new ThreadFactory() {       // 自定义线程工厂
        private final AtomicInteger count = new AtomicInteger(1);
        public Thread newThread(Runnable r) {
            return new Thread(r, "worker-" + count.getAndIncrement());
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

4.4 优势

线程池复用线程,避免频繁创建和销毁的开销;通过队列和拒绝策略控制资源使用;提供 Future、批量提交、超时控制等能力。这是传统平台线程场景下最主流的线程创建方式。

五、使用 CompletableFuture

5.1 基本用法

JDK 8 引入的 CompletableFuture 支持异步任务编排,默认使用 ForkJoinPool.commonPool():

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return "异步结果";
});

future.thenApply(String::toUpperCase)
      .thenAccept(System.out::println);

5.2 指定线程池

可以传入自定义 Executor:

ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture.supplyAsync(() -> "结果", executor)
                 .thenAcceptAsync(System.out::println, executor);

5.3 适用场景

适合编排多个异步任务之间的依赖关系,如串行、并行、组合、超时等。CompletableFuture 本身不是线程创建机制,而是任务编排工具,但它在底层依赖线程池来执行任务。

六、使用 ForkJoinPool

6.1 基本用法

JDK 7 引入的 ForkJoinPool 专为分治任务设计,核心是工作窃取算法:

ForkJoinPool pool = new ForkJoinPool();

class SumTask extends RecursiveTask<Long> {
    private final long[] array;
    private final int start, end;

    SumTask(long[] array, int start, int end) {
        this.array = array; this.start = start; this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= 1000) {
            long sum = 0;
            for (int i = start; i < end; i++) sum += array[i];
            return sum;
        }
        int mid = (start + end) / 2;
        SumTask left = new SumTask(array, start, mid);
        SumTask right = new SumTask(array, mid, end);
        left.fork();
        return right.compute() + left.join();
    }
}

Long result = pool.invoke(new SumTask(array, 0, array.length));

6.2 适用场景

适合可以递归拆分的计算密集型任务,如归并排序、大规模数值计算。Java 8 的并行流(parallelStream())底层也使用 ForkJoinPool.commonPool()。

6.3 与线程池的差异

ForkJoinPool 的每个工作线程维护一个双端队列,空闲线程从其他线程的队列尾部窃取任务。这种设计在分治场景下能最大化 CPU 利用率,但对普通 I/O 任务并不合适。

七、虚拟线程(JDK 21+)

7.1 基本用法

JDK 21 正式引入虚拟线程,创建方式与平台线程高度一致:

// 方式一:直接启动
Thread.startVirtualThread(() -> System.out.println("虚拟线程"));

// 方式二:Thread.Builder
Thread t = Thread.ofVirtual()
                .name("my-virtual")
                .start(() -> System.out.println("虚拟线程"));

// 方式三:虚拟线程执行器
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> handleRequest());
}

7.2 原理

虚拟线程由 JVM 管理,多个虚拟线程复用一个平台线程(载体线程)。当虚拟线程执行阻塞操作时,JVM 将其栈帧保存到堆中并卸载,载体线程转而执行其他虚拟线程。这避免了操作系统线程上下文切换的开销。

7.3 适用场景

虚拟线程适合 I/O 密集型场景,如 Web 服务处理大量并发请求。对于 CPU 密集型任务,虚拟线程不会带来收益,因为载体线程数量仍受限于 CPU 核心数。

7.4 注意事项

八、Thread.Builder API(JDK 21+)

8.1 基本用法

JDK 21 引入了 Thread.Builder 接口,提供统一的线程创建方式,同时支持平台线程和虚拟线程:

// 平台线程
Thread platformThread = Thread.ofPlatform()
    .name("platform-", 1)
    .priority(Thread.NORM_PRIORITY)
    .unstarted(runnable);

// 虚拟线程
Thread virtualThread = Thread.ofVirtual()
    .name("virtual-", 1)
    .unstarted(runnable);

8.2 ThreadFactory

Thread.Builder 也可以作为 ThreadFactory 使用:

ThreadFactory factory = Thread.ofVirtual().name("worker-", 0).factory();
ExecutorService executor = Executors.newThreadPerTaskExecutor(factory);

8.3 优势

Thread.Builder 统一了平台线程和虚拟线程的创建接口,支持链式配置名称、优先级、守护状态、未捕获异常处理器等属性,是 JDK 21 之后推荐的线程创建方式。

九、其他创建方式

9.1 Timer 与 TimerTask

java.util.Timer 可以在后台线程中调度任务:

Timer timer = new Timer("timer-thread", true);
timer.schedule(new TimerTask() {
    public void run() {
        System.out.println("定时任务");
    }
}, 1000, 2000);

Timer 内部只有一个线程,任务顺序执行,一个任务异常会导致整个 Timer 终止。JDK 5 之后推荐使用 ScheduledExecutorService 替代。

9.2 SwingWorker

在 Swing 桌面应用中,SwingWorker 用于在后台线程执行耗时任务,并在事件分发线程(EDT)中更新 UI:

new SwingWorker<String, Void>() {
    protected String doInBackground() {
        return longRunningTask();
    }
    protected void done() {
        try { label.setText(get()); } catch (Exception e) { }
    }
}.execute();

SwingWorker 内部使用线程池管理后台线程,doInBackground() 在工作线程执行,done() 在 EDT 执行。

9.3 parallelStream

Java 8 的并行流通过 ForkJoinPool.commonPool() 执行任务:

list.parallelStream()
    .map(this::process)
    .collect(Collectors.toList());

并行流不是直接创建线程的方式,而是一种声明式的并行执行机制,底层复用 Fork/Join 框架。

9.4 匿名内部类与 Lambda

严格来说,匿名内部类和 Lambda 不是独立的线程创建方式,而是 Thread 或 Runnable 的语法糖:

// 匿名内部类
new Thread(new Runnable() {
    public void run() { }
}).start();

// Lambda
new Thread(() -> { }).start();

十、各种方式对比

方式返回值异常处理线程复用适用场景
继承 Thread无无受检异常否简单演示
实现 Runnable无无受检异常否任务与线程解耦
Callable + FutureTask有可抛受检异常否需要返回值和异常
ExecutorService有(Future)通过 Future是通用并发任务
CompletableFuture有链式处理是(底层线程池)异步任务编排
ForkJoinPool有通过 Future是分治计算任务
虚拟线程有同平台线程不需要池化I/O 密集型高并发
Thread.Builder有同平台线程取决于类型JDK 21+ 统一接口
Timer无无受检异常单线程简单定时任务
SwingWorker有通过 get()是Swing 后台任务

十一、选型建议

无论选择哪种方式,核心原则是:任务逻辑与线程机制分离,线程资源通过池化管理,根据任务类型(CPU 密集 / I/O 密集)选择合适的并发模型。

十二、总结

Java 提供了从底层到高层的多种线程创建方式,反映了并发编程抽象层次的不断提升:

理解每种方式的原理和适用场景,有助于在实际开发中做出合理的技术选择,写出高效、可维护的并发程序。

以上就是Java中创建线程的六种方式详解的详细内容,更多关于Java创建线程方式的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文