实现runnable接口不自动解决资源共享问题,关键在于共用同一实例、同步控制共享状态、封装资源管理器,并区分线程私有与不可变对象。

实现Runnable接口本身并不自动解决资源共享问题,它只是提供了让多个线程共用同一任务对象的结构基础。真正实现“安全、可控、可预期”的资源共享,关键在于设计方式 + 同步机制 + 资源粒度把控。
共享的前提:用同一个Runnable实例启动多个线程
这是最基础也最容易被忽略的一点。只有当多个Thread对象构造时传入的是**同一个Runnable对象**,它们才可能操作该对象的成员变量。
- ✅ 正确做法:先 new 一个 MyRunnable 实例,再用它创建多个 Thread
- ❌ 错误做法:每个 Thread 都 new 一个独立的 MyRunnable(这样每个线程都有自己的 ticket、counter 等字段,根本没共享)
避免竞态:对共享状态加同步控制
多个线程同时读写同一个变量(如票数、计数器、列表),不加保护就会出现脏读、丢失更新、越界等现象。常见同步手段有:
- synchronized 方法:把整个 run() 或关键方法声明为 synchronized,简单但可能影响并发吞吐
-
synchronized 代码块:只锁定真正需要互斥的操作段,例如
synchronized(this) { if (ticket > 0) { System.out.println(...); ticket--; } } - java.util.concurrent 工具类:如 AtomicInteger 替代 int、ConcurrentHashMap 替代 HashMap、BlockingQueue 做生产者-消费者协作,比手动加锁更高效安全
资源封装与职责分离
把共享数据从 Runnable 中抽离出来,单独建模为一个“资源管理器”,能提升可维护性和复用性。
- 例如定义
TicketPool类,内部用 AtomicInteger 管理余票,提供trySell()方法返回是否售出成功 - Runnable 实现类只负责调用
ticketPool.trySell(),不直接操作原始字段 - 这样以后换成数据库库存、Redis 库存,只需替换 TicketPool 实现,Runnable 逻辑几乎不用改
注意非共享场景:局部变量和不可变对象天然线程安全
不是所有东西都需要“共享”。有些变量本就不该被共享:
- 循环变量 i、临时计算结果、方法内 new 的对象(如 StringBuilder),属于线程私有,无需同步
- String、Integer 等不可变对象,即使多线程引用同一个实例,也不会被修改,也不需加锁
- 真正需要关注的是:可变的、被多个线程读写的**共享可变状态**(shared mutable state)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











