Runnable 是 Java 中解耦任务与执行者的最轻量方式,只定义 run() 行为,不创建线程;需用 start() 启动并发,直接调用 run() 仅为同步执行;可被多线程共享,但有状态时须保证线程安全;线程池中 submit(Runnable) 返回 null 的 Future,取消需手动检查中断;Lambda 无参无受检异常,参数靠闭包捕获,异常需包装为 RuntimeException;解耦后仍需警惕 ThreadLocal 等隐式线程依赖。

Runnable 接口是 Java 中实现任务与执行者解耦最直接、最轻量的方式——它不创建线程,只定义 run() 里该做什么;线程怎么起、起几个、何时停,全由外部决定。
为什么不能直接 new Thread().run()?
调用 run() 方法只是普通方法调用,不会启动新线程,所有逻辑仍在当前线程(比如主线程)中同步执行。真正触发并发的是 start(),它通知 JVM 创建操作系统级线程并回调 run()。
- ✅ 正确:
new Thread(task).start() - ❌ 错误:
new Thread(task).run()(无并发,纯串行) - ⚠️ 注意:若任务需调试或单元测试,可显式调用
task.run(),但这是主动降级为同步执行,不是多线程
Runnable 实例能否被多个线程共享?
能,而且这是解耦的关键体现:一个 Runnable 对象可被多个 Thread 实例复用,甚至被线程池反复调度。
- 同一实例传给不同
Thread构造器,各自在独立线程中执行run() - 若任务内部有状态(如字段
private int count),必须自行保证线程安全;无状态的Runnable(如纯计算、只读操作)天然适合共享 - Lambda 表达式生成的
Runnable默认无状态,复用更安全:Runnable task = () -> System.out.println("hello");
用线程池执行 Runnable 时要注意什么?
线程池(如 ExecutorService)是生产环境推荐方式,但和裸用 Thread 有关键差异:
-
submit(Runnable)返回Future>,但get()永远返回null(因为Runnable无返回值) - 若需取消正在执行的任务,得在
run()内定期检查Thread.currentThread().isInterrupted(),并配合future.cancel(true) - 线程池可能复用线程,所以不要在
run()里做线程局部初始化(如ThreadLocal初始化),除非明确清理
Lambda 写法下如何传递参数或捕获异常?
Lambda 是 Runnable 最简洁的实现,但它受限于函数式接口契约:不能抛出受检异常(Exception),也不能直接带参。
- 参数通过闭包捕获:
String msg = "done"; pool.submit(() -> System.out.println(msg)); - 处理受检异常需包装:
pool.submit(() -> { try { Files.delete(Paths.get("tmp.txt")); } catch (IOException e) { throw new RuntimeException(e); } }); - 若需返回值或抛受检异常,应换用
Callable,而非硬套Runnable
真正容易被忽略的点是:解耦不等于“零耦合”。即使用了 Runnable,任务逻辑仍可能隐式依赖线程上下文(如 ThreadLocal、未同步的静态变量、单例状态)。这些依赖不会报错,但会在多线程复用时暴露竞态问题——解耦之后,更要盯紧状态归属。










