
volatile 不能替代 synchronized 的场景
当你只用 volatile 修饰一个计数器变量,却在多线程里反复执行 count++,结果大概率出错——这不是 volatile 不生效,而是它不保证复合操作的原子性。
-
count++实际包含“读取-修改-写入”三步,volatile只确保每次读写都直通主内存,但两线程仍可能同时读到旧值、各自加 1、再写回,最终只加了一次 - 适用于纯状态标记(如
isRunning)、单次写入后只读的配置项,或配合 CAS 操作(如AtomicInteger内部) - 若字段涉及多个变量协同更新(比如缓存 + 版本号),仅靠
volatile无法建立同步边界,必须用锁或synchronized块包裹整个逻辑段
synchronized 块的临界区范围要刚刚好
把整个方法用 synchronized 修饰看似省事,但容易拖慢吞吐量;而临界区太小,又漏掉关键共享点,照样引发内存可见性问题。
- 检查是否所有访问共享状态的路径都被同一把锁保护:比如对象内部有
cacheMap和lastAccessTime,二者需同时更新,则不能只锁 map 操作,而忽略时间戳赋值 - 避免在同步块内调用外部可重入或阻塞的方法(如 I/O、远程调用),否则会把锁持有时间不可控地拉长
- 锁对象别用
this或getClass(),优先用私有 final 对象(如private final Object lock = new Object();),防止外部误锁干扰同步边界
wait/notify 必须与 synchronized 配对使用
单独调用 wait() 或 notify() 会抛 IllegalMonitorStateException,这不是语法错误,而是 JVM 强制要求:必须先获得对象监视器(即进入 synchronized 块),才能触发等待/唤醒协议。
- 经典坑:在
synchronized(obj)块里调用了obj.wait(),但没处理InterruptedException,导致线程中断后锁未释放或状态错乱 - 永远用 while 循环判断条件,不用 if——因为虚假唤醒(spurious wakeup)真实存在,条件可能在 wait 返回时已不成立
-
notify()只唤醒一个线程,若多个消费者等待同一条件,应改用notifyAll(),否则可能永久挂起其余线程
ConcurrentHashMap 的弱一致性迭代器
你遍历 ConcurrentHashMap 的 keySet 时发现某些刚 put 的键没出现,或者 size() 返回值和实际条目数对不上——这不是 bug,是它为性能做的明确取舍。
立即学习“Java免费学习笔记(深入)”;
- 迭代器不阻塞写操作,因此可能跳过并发插入的元素,也可能重复看到某个元素(取决于具体实现版本),但绝不会抛
ConcurrentModificationException -
size()在高并发下返回估算值,因为它需要累加每个 segment 的大小,而中间可能有增删,所以不适合做精确条件判断(比如 “size() == 0” 判空) - 若需强一致性视图,应改用
Collection.toArray()获取快照,或换用Collections.synchronizedMap()(但要自己管理外部同步)











