线程安全问题源于共享数据竞争,而非runnable接口本身;应通过同步机制、不可变对象、threadlocal或并发工具类保障安全,并避免run()方法重入风险。

实现 Runnable 接口本身不会带来线程安全问题,问题出在多个线程共享并修改同一份数据时。关键不是“怎么写 Runnable”,而是“怎么用它来安全地操作共享资源”。
共享变量必须明确隔离或同步
当多个线程共用同一个 Runnable 实例(如售票窗口 new Window() 被传给三个 Thread),其中的成员变量(如 ticket = 100)就是共享数据。不加保护就会出现重卖、漏卖。
- 用
synchronized代码块包裹临界区,锁对象要唯一且一致(推荐用this或专用锁对象,不要每次 new 一个) - 避免把可变外部对象(比如 List、Map、自定义业务对象)直接暴露给多个线程共用;若必须共享,确保其本身是线程安全的(如
ConcurrentHashMap)或整体受锁保护 - 静态变量天然跨实例共享,更需谨慎——除非明确设计为全局状态,否则尽量避免在 Runnable 中使用非 final 静态字段
优先使用不可变或线程本地数据
不共享,就不用同步。这是最轻量的安全策略。
- 任务中所需的参数尽量通过构造方法传入,并设为
final;运行期间只读,无需加锁 - 临时计算结果、中间状态等,用局部变量(栈内分配),每个线程独享,天然线程安全
- 必要时用
ThreadLocal管理线程私有副本,比如数据库连接、用户上下文、计数器等
用高级并发工具替代手写同步
比起裸 synchronized,java.util.concurrent 提供了更清晰、更可控的方案。
- 对计数类场景,直接用
AtomicInteger、AtomicLong替代 int/long 字段,无需显式锁 - 需要复杂协调逻辑(如等待、超时、公平性控制),用
ReentrantLock配合Condition - 批量任务提交统一走
ExecutorService,配合Future或CompletableFuture管理结果,减少手动线程管理带来的风险
避免在 run() 中误调用或嵌套触发重入
run() 是普通方法,被同一线程多次调用(比如线程池复用 + 递归/回调)可能引发意外重入,尤其涉及第三方不可重入锁时。
- 不在
run()内部直接调用自身或其他可能再次进入该 Runnable 的逻辑 - 若必须支持重复进入,用
ThreadLocal<boolean></boolean>记录当前线程是否已持锁,防止重复lock() - 对关键操作添加简单校验,例如检查
Thread.currentThread().getName()是否符合预期线程命名规则,快速识别误执行场景
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











