1. 线程创建与启动的三种方式对比
三种创建方式的核心差异
Java 中创建并启动线程有三种常见路径,它们的本质区别在于:任务体写在哪里、能否拿到返回值、能否抛出受检异常。理解这三点的差异,比记住 API 更重要。
- 继承 Thread:重写
run(),任务体与线程对象耦合,无返回值,不能抛受检异常。 - 实现 Runnable:把任务体从线程对象中解耦,仍无返回值,不能抛受检异常。
- 实现 Callable + FutureTask:任务体可返回结果、可抛异常,通过
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-2 | JVM 自动命名,与创建顺序相关但不保证绝对对应 |
| 输出顺序 | main 与三个子线程的输出交错 | 线程调度不确定,不能假设固定顺序 |
| Callable 返回值 | 5050 | 1~100 累加和,由 futureTask.get() 获取 |
| get() 阻塞 | main 在 get() 处等待 | 若 call() 未完成,get() 会阻塞直到结果就绪 |
常见误区:调用
run() 而不是 start(),程序仍能运行,但任务体在当前线程执行,不会创建新线程。验证方法:在 run() 中打印 Thread.currentThread().getName(),若输出 main 则说明没有真正启动新线程。
选择建议
- 需要返回值或需要抛出受检异常 → 选 Callable + FutureTask。
- 只是执行一个无返回值的任务,且希望任务体与线程对象解耦 → 选 Runnable。
- 继承 Thread 仅适合极简场景,且会占用唯一的继承名额,一般不推荐。