同步块保护共享变量的关键是共用同一锁对象,优先使用并发工具类替代手动同步,警惕非原子复合操作,合理使用ThreadLocal并及时清理。

用 synchronized 块保护共享变量的临界区
竞争条件本质是多个线程同时读写同一变量且缺乏同步,导致结果不可预测。最直接的解法是用 synchronized 显式围住所有访问该变量的代码段,确保同一时刻只有一个线程能执行。
注意:锁对象必须是所有线程共用的同一个实例,不能每次 new 一个新对象;静态变量要用 Class.class 或静态 final 对象做锁,避免锁错对象。
- 错误写法:
new Object()作为锁 —— 每次新建对象,锁失效 - 推荐写法:
private static final Object lock = new Object();,然后synchronized(lock) { ... } - 若操作的是实例变量,可用
this,但要确保调用方不会暴露或误用该实例作其他同步
优先使用 java.util.concurrent 包里的线程安全类型
不是所有共享状态都需要手写锁。像计数、队列、Map 这类高频场景,JDK 已提供成熟替代品,比手动同步更简洁、更少出错。
例如:用 AtomicInteger 替代 int 计数器,用 ConcurrentHashMap 替代 HashMap,用 BlockingQueue 实现生产者-消费者通信。
立即学习“Java免费学习笔记(深入)”;
-
AtomicInteger.incrementAndGet()是原子操作,无需额外同步 -
ConcurrentHashMap支持高并发读,写操作也只锁段(或使用 CAS),性能远超synchronized(new HashMap()) - 别在
ConcurrentHashMap上加synchronized—— 锁粒度错配,反而拖慢性能
警惕“看似线程安全”的隐藏竞态
有些操作表面看是单行代码,实则非原子:比如 list.size() > 0 && list.get(0),中间可能被其他线程清空;又如双重检查单例中忘了加 volatile 修饰 instance 字段,会导致部分线程看到未初始化完成的对象。
- 复合操作(读-改-写)必须整体同步,不能只锁其中一部分
- 懒汉式单例必须用
volatile+synchronized,否则有指令重排序风险 - 迭代
ArrayList时即使加了锁,也要注意外部修改可能触发ConcurrentModificationException—— 改用CopyOnWriteArrayList或保证迭代期间无写入
用 ThreadLocal 隔离线程私有状态
当每个线程需要自己的一份副本(比如数据库连接、SimpleDateFormat、用户上下文),ThreadLocal 是比加锁更轻量的方案 —— 它从根源上消除共享,自然没有竞争。
但要注意内存泄漏:在线程池场景下,线程长期存活,ThreadLocal 的值若不显式 remove(),会一直持有引用,导致 GC 不掉。
- 务必在业务逻辑末尾调用
threadLocal.remove(),尤其在 Filter、Interceptor、Runnable 结束前 - 不要把大对象塞进
ThreadLocal,避免堆外内存压力或 OOM -
ThreadLocal.withInitial()创建时更安全,但依然要清理










