实现runnable接口本身不保证状态一致性,关键在于共用同一实例、同步控制共享状态、封装资源管理器,并区分线程私有与不可变对象。

实现 Runnable 接口本身不保证状态一致性,关键在于如何设计共享对象、控制访问路径和选择同步策略。多个线程共用同一个 Runnable 实例时,其成员变量才构成真正共享资源;若每个线程都 new 一个新实例,就谈不上共享,更无一致性可言。
共用同一 Runnable 实例是前提
只有让多个 Thread 构造时传入同一个 Runnable 对象,它们的操作才会落在同一组字段上。比如卖票场景中,票数 count 必须是该实例的成员变量,且所有线程启动时都使用这一个对象。
- ✅ 正确:MyRunnable task = new MyRunnable(); new Thread(task).start(); new Thread(task).start();
- ❌ 错误:new Thread(new MyRunnable()).start(); new Thread(new MyRunnable()).start(); —— 每个线程操作的是独立副本
对共享状态加同步控制
当多个线程读写同一变量(如 int count、List items),必须防止竞态条件。常见做法有:
- 用 synchronized 方法或代码块包裹临界区,确保同一时刻只有一个线程执行修改逻辑
- 用 ReentrantLock 提供更灵活的锁控制,比如可中断、超时尝试获取
- 避免只靠 volatile:它能保证可见性,但不能解决 i++ 这类非原子操作的丢失更新问题
优先使用原子类替代普通变量
对于简单计数、标志位等场景,AtomicInteger、AtomicBoolean 等类比加锁更轻量,内部基于 CAS 实现无锁原子更新。
- 适合:count++、flag.set(true)、state.compareAndSet(0, 1)
- 注意:复合操作(如“先读再判再改”)仍需同步,CAS 无法自动保障整个逻辑的原子性
封装共享资源,限制直接访问
把共享状态包装进专门的资源管理器中,对外只暴露线程安全的操作接口。例如:
- 将 ticket 列表封装为 TicketPool 类,内部用 ConcurrentHashMap 或加锁的 ArrayList
- Runnable 中只调用 pool.takeOne()、pool.isSoldOut(),不直接操作字段
- 这样既降低耦合,又便于统一维护同步策略
不复杂但容易忽略:一致性不是靠接口决定的,而是由对象生命周期、访问方式和同步机制共同保障的。Runnable 只是任务契约,真正的安全得靠你来设计。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











