Runnable接口本身不启动线程,需传入Thread并调用start();直接调用run()是同步执行;Lambda可简化实现;run()无返回值且不能抛受检异常;多线程共享同一实例时需注意状态安全。

Runnable接口必须配合Thread才能启动线程
实现 Runnable 接口本身不会创建或启动线程,它只是定义了线程要执行的任务逻辑。真正触发并发执行的,是把 Runnable 实例传给 Thread 构造函数并调用 start() 方法。
常见错误是直接调用 run() 方法——这等同于普通方法调用,仍在当前线程同步执行,完全不触发多线程:
Runnable task = () -> System.out.println("hello");
task.run(); // ❌ 串行执行,无并发
正确做法是:
- new Thread(
task).start(); - 或先构造再启动:
Thread t = new Thread(task); t.start();
Lambda表达式让Runnable写法更简洁
从 Java 8 开始,Runnable 是函数式接口(只有一个抽象方法 run()),可直接用 Lambda 表达式实例化,省去匿名内部类或独立类的样板代码。
立即学习“Java免费学习笔记(深入)”;
对比写法:
// 传统匿名内部类
new Thread(new Runnable() {
public void run() { System.out.println("old"); }
}).start();
// Lambda(推荐)
new Thread(() -> System.out.println("new")).start();
注意:Lambda 中若需访问外部局部变量,该变量必须是 final 或“事实上的 final”(声明后未被重新赋值)。
Runnable不能直接返回结果或抛出受检异常
Runnable.run() 方法签名是 void run(),且不声明任何 checked exception。这意味着:
- 无法通过
return返回计算结果(如 int、String) - 若任务中可能抛出
IOException等受检异常,必须在内部用try-catch捕获,不能向上抛出 - 若需要返回值或传播异常,应改用
Callable配合FutureTask或线程池
例如以下写法会编译失败:
Runnable bad = () -> {
Files.readAllBytes(Paths.get("a.txt")); // ❌ 编译报错:Unhandled exception type IOException
};
多个Thread共享同一个Runnable实例时要注意状态安全
一个 Runnable 实例可被多个 Thread 同时使用,如果它内部持有可变成员变量(比如计数器、缓存 map),就极易引发竞态条件。
示例问题代码:
class Counter implements Runnable {
int count = 0;
public void run() {
for (int i = 0; i < 1000; i++) count++; // ❌ 非原子操作,结果不可预期
}
}
// 多个线程共用同一实例
Runnable r = new Counter();
new Thread(r).start();
new Thread(r).start();
解决方式取决于场景:
- 优先设计为无状态(stateless):所有数据来自参数或局部变量
- 若必须共享状态,加同步(
synchronized块/方法)或用原子类(如AtomicInteger) - 避免复用:每次 new 一个新实例,适用于任务间无需共享状态的场景
实际开发中,误以为“每个线程都有自己的 Runnable 副本”是最隐蔽的并发 bug 来源之一。









