volatile不能保证原子性,仅确保可见性和禁止重排序,对i++等复合操作无效;synchronized锁的是对象而非语法结构;ReentrantLock需手动管理且必须在finally中unlock;ConcurrentHashMap单操作线程安全但复合操作仍需同步。

volatile 不能保证原子性,别把它当锁用
看到 volatile 就以为能防并发问题,这是最常见的误判。它只保证变量的可见性和禁止指令重排序,对 i++ 这种读-改-写操作完全无效。比如多个线程同时执行 counter++(counter 是 volatile int),最终结果大概率小于预期。
- 适用场景:状态标志位,如
running = true、isShutdown - 不适用场景:计数器、累加、集合操作、复合逻辑判断
- 替代方案:需要原子更新时,优先用
AtomicInteger、AtomicReference等原子类
synchronized 锁的是对象,不是代码块或方法名
写 synchronized(void method()) 或给不同实例的方法加锁,根本拦不住并发——因为锁对象不同。关键看锁住的是哪个实例或类,而不是“写了 synchronized”这个动作本身。
- 实例方法加
synchronized→ 锁当前对象(this) - 静态方法加
synchronized→ 锁当前类的Class对象 - 同步代码块如
synchronized(obj)→ 锁的是obj引用指向的对象 - 陷阱:用
new Object()作为锁对象,每次新建一个,等于没锁
ReentrantLock 比 synchronized 更灵活,但也更易出错
ReentrantLock 支持可中断等待、超时获取、公平锁、多条件队列,但必须手动 lock() 和 unlock(),且 unlock() 必须放在 finally 块里,否则极易死锁或资源泄漏。
- 正确写法:
lock.lock(); try { // 临界区 } finally { lock.unlock(); } - 别在
try外调用lock(),也别漏掉finally - 用
lock.tryLock(1, TimeUnit.SECONDS)可避免无限等待,适合有响应时间要求的场景 - 公平锁(
new ReentrantLock(true))会降低吞吐量,仅在需要严格 FIFO 时考虑
ConcurrentHashMap 不是万能的,复合操作仍需额外同步
ConcurrentHashMap 的 get、put、remove 单个操作是线程安全的,但像“先检查再插入”(if (!map.containsKey(k)) map.put(k, v);)这种两步操作,依然存在竞态条件。
立即学习“Java免费学习笔记(深入)”;
- 用
computeIfAbsent替代 get + put 判断逻辑 - 用
replace(K, V, V)实现带条件的更新 - 涉及多个 key 或跨 map 操作时,必须引入外部锁(如
synchronized(map)或ReentrantLock) - 注意:
size()返回的不是实时精确值,高并发下可能滞后;如需准确计数,考虑LongAdder









