Runnable接口不创建线程,仅定义任务;真正创建并启动线程的是Thread类或线程池;直接调用run()无并发效果,须用start();匿名类、Lambda、独立类三种实现方式各适配不同场景。

Runnable接口本身不创建线程,它只定义任务
这是最常被误解的一点:Runnable 是一个函数式接口,只规定了 run() 方法签名,它不启动线程、不管理生命周期、也不持有线程资源。真正创建并启动线程的是 Thread 类或线程池。
常见错误现象:直接调用 myRunnable.run() —— 这只是普通方法调用,仍在当前线程执行,**完全不并发**。
- 正确做法是把
Runnable实例传给Thread构造器,再调用thread.start() -
start()才会触发 JVM 创建新线程并回调run() - 重复调用
start()会抛出IllegalThreadStateException
三种主流写法及适用场景
实际开发中,Runnable 的实现方式影响可读性、复用性和维护成本。
匿名内部类(适合一次性简单任务):
立即学习“Java免费学习笔记(深入)”;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello from thread: " + Thread.currentThread().getName());
}
}).start();
Lambda(Java 8+,推荐用于无状态逻辑):
new Thread(() -> {
System.out.println("Hello from lambda thread");
}).start();
独立类实现(适合需复用、含状态或需测试的场景):
public class CounterTask implements Runnable {
private final int count;
public CounterTask(int count) { this.count = count; }
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
// 使用
new Thread(new CounterTask(5)).start();
- Lambda 不能捕获非 final 或“事实上 final”的变量;若需修改外部变量,用独立类更安全
- 独立类可被单元测试、可注入依赖、可加日志/监控埋点
- 匿名类在调试时类名难识别(如
MyApp$1),不利于排查线程 dump
为什么不用继承 Thread 类而选 Runnable?
这不是风格偏好,而是 Java 语言机制和工程实践的硬约束。
- Java 不支持多继承,如果类已继承其他父类(如
Frame、Activity),就无法再 extendsThread -
Runnable更符合“单一职责”:任务逻辑(what)与执行机制(how)分离 - 线程池(如
ExecutorService)只接受Runnable或Callable,不接受Thread子类 - 直接 new Thread() 容易造成线程资源失控;用
Runnable+ 线程池才是生产环境标准做法
容易忽略的线程安全陷阱
很多人以为只要用了 Runnable 就“多线程了”,却没意识到共享数据的风险。
- 多个
Thread实例共用同一个Runnable对象(尤其用 Lambda 捕获外部变量时),其字段可能被并发修改 - 即使每个线程 new 一个
Runnable,若它们操作静态变量、单例对象或传入的共享集合,仍需同步 -
System.out.println()虽然线程安全,但输出可能交错(如 “A1B2C3”),不代表业务逻辑安全 - 不要在
run()中吞掉异常:未捕获的RuntimeException会导致线程静默终止,且不会传播到主线程
线程真正的复杂点不在怎么启动,而在共享状态的可见性、原子性和有序性——这些靠 Runnable 接口本身解决不了,得靠 synchronized、volatile、java.util.concurrent 工具类来兜底。










