Runnable本身不创建线程,需通过Thread.start()或线程池执行;调用run()仅在当前线程运行,无并发效果;主流用法为Thread包裹、线程池提交、Lambda简写;Callable支持返回值和受检异常,返回Future;需注意共享变量同步、非线程安全对象复用、线程池关闭及ThreadLocal上下文传递问题。

直接用 Runnable 接口本身不能“创建线程”,它只定义任务;真正创建并启动线程,得靠 Thread 类或线程池。
为什么不能直接 new Thread(Runnable) 就算完事?
很多人写 new Thread(new MyRunnable()).run(),结果任务在主线程执行、毫无并发效果——因为调用了 run() 而非 start()。
-
run()是普通方法,谁调用就在谁的线程里执行 -
start()才触发 JVM 开新线程,并在新线程中自动调用run() - 重复调用
start()会抛IllegalThreadStateException
三种主流用法及适用场景
实际开发中,Runnable 几乎不会单独出现,而是配合具体执行机制:
- 手动管理:用
Thread包裹后调用start()—— 适合简单、一次性、低频任务(如 GUI 初始化回调) - 线程池提交:
executor.submit(runnable)或executor.execute(runnable)—— 绝大多数业务场景首选,复用线程、避免资源泄漏 - Lambda 简写:
executor.execute(() -> { /* do something */ });—— JDK 8+ 后,只要Runnable只有一个抽象方法,就能用 lambda,本质仍是Runnable实例
Runnable 和 Callable 的关键区别在哪?
如果任务需要返回值或抛受检异常,Runnable 就不够用了:
立即学习“Java免费学习笔记(深入)”;
-
Runnable.run()返回void,且不能声明抛出受检异常(如IOException) -
Callable.call()返回泛型V,可声明抛出任意异常 - 线程池对两者支持不同:
execute()只接受Runnable;submit()两个都收,但传Callable会返回Future
例如:Future —— 这个 "done" 就是 call() 的返回值,而 Runnable 做不到这点。
容易被忽略的生命周期与状态问题
Runnable 实例本身无状态,但它的执行上下文有。常见疏漏:
- 共享变量没加同步或用
volatile,导致多线程读写错乱 - 把非线程安全对象(如
SimpleDateFormat)作为Runnable成员复用,引发格式化异常 - 线程池 shutdown 后仍提交任务,抛
RejectedExecutionException,需检查isShutdown()或用try-catch
最麻烦的是:任务逻辑里隐式依赖了 ThreadLocal 或当前线程身份(比如日志 MDC),换线程执行就丢上下文——这种必须显式传递或重置。










