很多 Android / Java 开发者,在学习并发时都会经历一个阶段:
“代码能写,但概念越学越乱。”Thread、Runnable、Callable、Future、Worker、线程池……
看起来都和“线程”有关,但又好像都不一样。
这篇文章的目标只有一个:
用一页认知,彻底终结 Java 并发概念混乱。
一、先给结论:只记住这三句话
如果你只想记住最核心的东西,请记住这三句:
① Thread 才是真正的线程
② Runnable / Callable 都只是“任务”
③ 线程池 = 一堆 Thread(Worker)反复执行一堆 Runnable
后面所有内容,都是这三句话的展开。
二、谁才是“真正的线程”?
✅ Thread
Thread才是真正被 JVM / CPU 调度的执行单元start()才会创建新线程run()只是线程启动后的入口方法
new Thread(() -> { System.out.println("run in new thread"); }).start();判断标准很简单:
能不能被 CPU 调度?
能 → Thread
不能 → 不是线程
三、Runnable 到底是什么?(非常容易被误解)
Runnable ≠ 线程
class MyTask implements Runnable { @Override public void run() { System.out.println("task run"); } }Runnable的本质是:
- 一个任务
- 一段可被线程执行的代码
- 自己不会创建线程
- 自己不会并发执行
⚠️ 注意下面这个常见误区:
Runnable r = () -> System.out.println("run"); r.run(); // 不是多线程!这只是一个普通方法调用。
四、Callable 又是什么?和 Runnable 有什么区别?
Callable 的特点
- 有返回值
- 可以抛出异常
Callable<Integer> c = () -> 123;
但重点是:
Callable 也不是线程,甚至不能直接交给 Thread
五、Callable 是怎么跑起来的?
关键角色:FutureTask
Callable<Integer> c = () -> 123; FutureTask<Integer> task = new FutureTask<>(c); new Thread(task).start(); System.out.println(task.get());这里发生了什么?
FutureTask
实现了Runnable
同时实现了Future
- Thread 只认识
Runnable - Callable必须先被包装成 FutureTask
FutureTask = Runnable + 结果容器
六、Thread 和 Runnable / Callable 的真正关系
一句话总结:
Thread 决定“谁来跑”
Runnable / Callable 决定“跑什么”
| 角色 | 是线程吗 | 作用 |
|---|---|---|
| Thread | ✅ | 执行单元 |
| Runnable | ❌ | 无返回值任务 |
| Callable | ❌ | 有返回值任务 |
| FutureTask | ❌ | 任务 + 结果 |
七、线程池里到底发生了什么?
这是理解并发的关键一步。
线程池的本质结构
线程池 ├── Worker(Thread) × N └── Runnable / FutureTask(任务)Worker 是什么?
class Worker extends Thread { @Override public void run() { while (true) { Runnable task = takeTask(); task.run(); } } }- Worker 是线程
- Worker 是常驻的
- 一个 Worker 会执行很多个 Runnable
八、为什么 Worker.run 里又调用 Runnable.run?
因为:
- Thread 的
run():线程生命周期 - Runnable 的
run():业务逻辑
线程(Worker) └── run() ← 线程入口(只进一次) └── while(true) └── task.run() ← 业务任务(进很多次)线程和任务被彻底解耦,这就是线程池存在的意义。
九、execute / submit 的本质区别
| 方法 | 返回值 | 本质 |
|---|---|---|
| execute(Runnable) | 无 | 丢任务 |
| submit(Runnable) | Future | Runnable → FutureTask |
| submit(Callable) | Future | Callable → FutureTask |
submit 内部一定会创建 FutureTask
十、三个最常见误区(一次性清空)
❌ 误区 1:实现 Runnable 就是线程
✔ 真相:Runnable 只是任务
❌ 误区 2:run() 会开启新线程
✔ 真相:只有 start()
❌ 误区 3:Thread 实现 Callable
✔ 真相:Thread 实现的是 Runnable
十一、一句压箱底总结(建议背下来)
Thread 是线程
Runnable / Callable 是任务
Worker 是线程池里的 Thread
Callable 要靠 FutureTask 才能跑
submit 一定会返回 Future
写在最后
很多并发问题,并不是 API 不会用,
而是“线程”和“任务”的边界没想清楚。
当你真正理解了:
- 线程是有限资源
- 任务是无限的
- 线程必须被池化、被管理
你就已经迈入了后端 / 系统级并发思维。