1. 线程创建与启动的三种方式对比

线程创建与启动的三种方式对比 学习卡片

三种创建方式的核心差异

Java 中创建并启动线程有三种常见路径,它们的本质区别在于:任务体写在哪里、能否拿到返回值、能否抛出受检异常。理解这三点的差异,比记住 API 更重要。

  • 继承 Thread:重写 run(),任务体与线程对象耦合,无返回值,不能抛受检异常。
  • 实现 Runnable:把任务体从线程对象中解耦,仍无返回值,不能抛受检异常。
  • 实现 Callable + FutureTask:任务体可返回结果、可抛异常,通过 FutureTask.get() 获取返回值。
三种线程创建方式:任务体位置与返回值能力对比 方式一:继承 Thread class MyThread extends Thread 重写 run() 写任务体 new MyThread().start() ✗ 无返回值 ✗ 不能抛受检异常 任务体与线程对象强耦合 方式二:实现 Runnable class MyTask implements Runnable 实现 run() 写任务体 new Thread(task).start() ✗ 无返回值 ✗ 不能抛受检异常 任务体与线程对象解耦 方式三:Callable + FutureTask class MyCallable implements Callable<T> 实现 call() 写任务体 new Thread(futureTask).start() ✓ 有返回值 ✓ 可抛受检异常 futureTask.get() 阻塞取结果 三种方式的共同本质:start() 才是启动线程的唯一入口 start() → JVM 创建新线程 → 新线程自动调用 run()(Callable 经 FutureTask 适配为 run()) 直接调用 run() 只是普通方法调用,仍在当前线程执行,不会创建新线程 执行顺序观察:主线程与子线程交替输出,顺序不确定 main: 启动子线程 子线程: 执行任务体 futureTask.get(): 阻塞等待返回值 注:子线程与主线程谁先输出取决于调度,不可假设固定顺序

三种方式的完整实现与验证

下面用一个可运行程序同时演示三种方式,并观察输出顺序与返回值差异。

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

public class ThreadCreateDemo {

    // 方式一:继承 Thread
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("[Thread] 子线程执行,名称=" + getName());
        }
    }

    // 方式二:实现 Runnable
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("[Runnable] 子线程执行,名称=" + Thread.currentThread().getName());
        }
    }

    // 方式三:实现 Callable,带返回值
    static class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("[Callable] 子线程执行,名称=" + Thread.currentThread().getName());
            int sum = 0;
            for (int i = 1; i <= 100; i++) sum += i;
            return sum; // 返回 1~100 的和
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("main 线程启动,名称=" + Thread.currentThread().getName());

        // 方式一
        MyThread t1 = new MyThread();
        t1.start();

        // 方式二
        Thread t2 = new Thread(new MyRunnable());
        t2.start();

        // 方式三
        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
        Thread t3 = new Thread(futureTask);
        t3.start();

        // 获取 Callable 的返回值(会阻塞直到 call() 执行完毕)
        Integer result = futureTask.get();
        System.out.println("[main] Callable 返回值 = " + result);

        System.out.println("main 线程结束");
    }
}
运行方式:保存为 ThreadCreateDemo.java,执行 javac ThreadCreateDemo.java 后运行 java ThreadCreateDemo。

结果判读要点

观察项预期现象说明
子线程名称Thread-0 / Thread-1 / Thread-2JVM 自动命名,与创建顺序相关但不保证绝对对应
输出顺序main 与三个子线程的输出交错线程调度不确定,不能假设固定顺序
Callable 返回值50501~100 累加和,由 futureTask.get() 获取
get() 阻塞main 在 get() 处等待若 call() 未完成,get() 会阻塞直到结果就绪
常见误区:调用 run() 而不是 start(),程序仍能运行,但任务体在当前线程执行,不会创建新线程。验证方法:在 run() 中打印 Thread.currentThread().getName(),若输出 main 则说明没有真正启动新线程。

选择建议

  • 需要返回值或需要抛出受检异常 → 选 Callable + FutureTask。
  • 只是执行一个无返回值的任务,且希望任务体与线程对象解耦 → 选 Runnable。
  • 继承 Thread 仅适合极简场景,且会占用唯一的继承名额,一般不推荐。