资源共享需共用同一runnable实例,同步应精准作用于共享变量,优先使用java.util.concurrent工具类,将共享资源抽离为独立类以实现职责分离,非共享变量无需加锁。

实现 Runnable 接口本身不等于资源共享,它只是提供了共享的“结构可能性”。真正优雅的关键,在于让共享自然发生、安全可控、职责清晰,而不是靠堆同步块硬扛。
共用同一个实例是共享的前提
多个线程只有操作同一个 Runnable 对象的成员变量,才谈得上共享。如果每个 Thread 都 new 一个新 Runnable,那变量各自独立,根本没交集。
- ✅ 正确:先 new MyTask(),再用它构造 Thread t1 = new Thread(task); Thread t2 = new Thread(task);
- ❌ 错误:Thread t1 = new Thread(new MyTask()); Thread t2 = new Thread(new MyTask()); —— 这样 ticket、counter 全是两套,互不影响
同步要精准,别一把锁锁全场
共享状态(比如余票数、计数器)必须保护,但锁的范围和粒度决定性能与可读性。
- 避免无脑 synchronized run():整个方法加锁会串行化执行,吞吐骤降
- 优先用 synchronized(this) { ... } 包裹真正读写共享变量的代码段,例如 if (ticket > 0) { System.out.println(...); ticket--; }
- 更推荐 java.util.concurrent 工具类:用 AtomicInteger 替代 int,用 ConcurrentLinkedQueue 替代 ArrayList,天然线程安全且无显式锁
把资源抽出来,单独建模
把 ticket、counter 这类共享数据硬塞在 Runnable 里,会导致逻辑混杂、难以替换、测试困难。优雅的做法是分离关注点。
- 定义 TicketPool 类,内部封装 AtomicInteger 和 trySell() 方法,返回是否售出成功
- Runnable 实现类只调用 pool.trySell(),不碰原始字段
- 以后换成 Redis 库存或数据库库存,只需重写 TicketPool,任务逻辑完全不动
分清哪些根本不用共享
不是所有东西都要“被共享”。很多变量天生就是线程私有或不可变,强行加锁反而画蛇添足。
- 方法内声明的变量(如 for (int i = 0; i
- String、Integer、LocalDateTime 等不可变对象,即使多个线程引用同一实例,也不会被修改,也不需加锁
- 真正需要同步的,仅限:可变的、被多个线程读写的、跨方法/跨类访问的状态
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











