菜单

Administrator
发布于 2026-05-18 / 2 阅读
0
0

Java 中创建线程的方法

Java 中创建线程的多种方法详解

前言

多线程编程是 Java 并发编程的核心内容之一。Java 从语言层面和标准库层面提供了多种创建线程的方式,每种方式都有其适用的场景和优劣。本文将详细介绍 Java 中常见的五种创建线程的方法,并通过代码示例和对比分析帮助你全面掌握它们。


一、继承 Thread

原理

Thread 类本身实现了 Runnable 接口,通过继承 Thread 并重写其 run() 方法,即可定义一个线程任务。创建实例后调用 start() 方法启动线程。

代码示例

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 运行中...");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();  // 启动线程(不能直接调用 run())
        t2.start();
    }
}

特点

优势 劣势
简单直观,适合快速实现 Java 单继承,继承了 Thread 就无法继承其他类
可以直接使用 Thread 类的方法(如 getName() 任务与线程耦合在一起,复用性差
适合简单的单次任务 多个线程共享同一任务时不够灵活

二、实现 Runnable 接口

原理

定义一个类实现 Runnable 接口,实现 run() 方法,然后将该实例作为参数传递给 Thread 构造器。这是推荐的方式,因为 Java 支持接口的多实现。

代码示例

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 运行中...");
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        MyRunnable task = new MyRunnable();
        Thread t1 = new Thread(task, "线程-A");
        Thread t2 = new Thread(task, "线程-B");
        t1.start();
        t2.start();
    }
}

特点

优势 劣势
解耦:任务与线程分离,同一个 Runnable 实例可被多个线程执行 没有返回值
可以继承其他类(接口的灵活性) 无法抛出受检异常(checked exception)
更适合线程池场景 代码略显冗余(需要额外创建 Thread 对象)

三、实现 Callable 接口 + FutureTask

原理

Runnable 的缺陷是没有返回值且不能抛出受检异常Callable<V> 接口解决了这个问题:它的 call() 方法有返回值,且可以抛出异常。配合 FutureTask(它实现了 RunnableFuture,即同时是 RunnableFuture)可以将 Callable 交给线程执行并获取结果。

代码示例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + " 计算中...");
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
            // 模拟耗时
            Thread.sleep(10);
        }
        return sum;  // 返回 1+2+...+100 的结果
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        // 创建 Callable 任务
        MyCallable callable = new MyCallable();
        // 包装成 FutureTask(Runnable 的实现类)
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 交给线程执行
        Thread t = new Thread(futureTask, "计算线程");
        t.start();

        // 主线程可以继续做其他事情...
        System.out.println("主线程继续工作...");

        try {
            // 获取 Callable 的返回结果(阻塞等待)
            Integer result = futureTask.get();
            System.out.println("计算结果: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

特点

优势 劣势
支持返回值,通过 Future.get() 获取 Future.get() 是阻塞操作,可能造成线程阻塞
支持抛出受检异常,调用方可以捕获处理 代码稍复杂,需要额外创建 FutureTask
支持取消任务(Future.cancel() 不直接配合线程池使用时不够简洁

四、线程池(ExecutorService

原理

通过 java.util.concurrent.Executors 工厂类创建线程池,提交 RunnableCallable 任务。线程池复用线程,避免频繁创建/销毁线程的开销,是生产环境中最推荐的方式。

代码示例

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 1. 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 2. 提交 Runnable 任务
        executor.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " 执行 Runnable 任务");
        });

        // 3. 提交 Callable 任务(有返回值)
        Future<Integer> future = executor.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " 执行 Callable 任务");
            return 42;
        });

        try {
            Integer result = future.get(2, TimeUnit.SECONDS);
            System.out.println("Callable 返回结果: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 4. 批量提交任务
        for (int i = 0; i < 5; i++) {
            int taskId = i;
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() +
                        " 执行任务 #" + taskId);
            });
        }

        // 5. 关闭线程池(优雅关闭)
        executor.shutdown();
        try {
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                executor.shutdownNow();  // 强制关闭
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }
    }
}

常见的线程池类型

工厂方法 特点 适用场景
newFixedThreadPool(n) 固定 n 个线程,任务队列无界 线程数固定的场景
newCachedThreadPool() 可缓存的线程池,按需创建,自动回收闲置线程 大量短期异步任务
newSingleThreadExecutor() 单线程,保证任务按顺序执行 需要顺序执行的任务
newScheduledThreadPool(n) 支持定时/周期性任务 定时任务、延迟任务
newWorkStealingPool() JDK 8+,工作窃取算法,并行度与 CPU 核数相关 大任务拆分为小任务并行处理

⚠️ 生产环境建议:直接使用 ThreadPoolExecutor 构造器显式指定参数,而非 Executors 工厂方法。因为工厂方法的默认参数(如 Integer.MAX_VALUE 的队列长度或最大线程数)可能在极端场景下引发 OOM。


五、Lambda 表达式 / 匿名内部类

原理

RunnableCallable 都是函数式接口(Functional Interface,只包含一个抽象方法的接口),因此可以直接使用 Lambda 表达式或匿名内部类来简化代码,无需定义单独的类

代码示例

public class LambdaThreadDemo {

    public static void main(String[] args) {
        // ----- 方式 1:匿名内部类 + Runnable -----
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 匿名内部类");
            }
        }, "匿名字段-A");

        // ----- 方式 2:Lambda + Runnable(最简洁)-----
        Thread t2 = new Thread(() ->
                System.out.println(Thread.currentThread().getName() + " Lambda"),
                "Lambda-B");

        // ----- 方式 3:Lambda + 线程池 -----
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.execute(() -> System.out.println(
                Thread.currentThread().getName() + " 线程池 + Lambda"));

        // ----- 方式 4:Lambda + Callable -----
        Future<String> future = pool.submit(() -> {
            Thread.sleep(500);
            return "Callable 返回结果";
        });

        // ----- 方式 5:方法引用(Method Reference)-----
        pool.execute(LambdaThreadDemo::printMessage);

        t1.start();
        t2.start();
        pool.shutdown();
    }

    private static void printMessage() {
        System.out.println(Thread.currentThread().getName() + " 方法引用");
    }
}

特点

优势 劣势
代码极度简洁,无需额外定义类 逻辑复杂时不易阅读
适合简单、一次性的任务 调试时堆栈信息不够直观
与线程池配合使用非常方便 相同逻辑无法复用

六、五种方法横向对比

维度 extends Thread implements Runnable Callable + FutureTask 线程池 ExecutorService Lambda / 匿名
代码简洁度 ⭐⭐⭐ ⭐⭐ ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
任务与线程解耦
支持返回值 ✅ (配合 Callable)
支持异常抛出
线程复用
推荐使用场景 学习/简单 demo 单次任务、小型项目 需要获取异步结果 生产环境首选 简单快速实现

七、总结与建议

  1. 学习阶段:四种方式都应掌握,理解线程的本质 —— Thread 是线程的抽象,Runnable/Callable 是任务的抽象。

  2. 小型项目或简单场景:使用 Runnable + Lambda 最为简洁高效。

  3. 需要获取异步结果:使用 Callable + FutureTask 或配合线程池的 submit() 方法。

  4. 生产环境始终优先使用线程池ThreadPoolExecutorExecutorService),避免直接 new Thread()。线程池可以控制并发度、复用线程、减少资源开销。

  5. 避免继承 Thread:除非你需要重写 Thread 类中除 run() 以外的其他方法(如 getName() 等可以不依赖当前线程上下文直接获取),否则优先实现 Runnable


“Favor composition over inheritance.” —— 在创建线程这件事上,优先实现 Runnable/Callable 接口而非继承 Thread 类,是这条设计原则最好的体现。


评论