引子:一个缓存系统的并发灾难 假设你正在开发一个高并发的缓存系统,使用HashMap存储缓存数据。看似简单的设计,却可能导致系统崩溃。
场景:本地缓存的并发问题 /** * 方案1:使用HashMap(线程不安全) */ public class UnsafeCache { private Map<String, String> cache = new HashMap<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } public static void main(String[] args) throws InterruptedException { UnsafeCache cache = new UnsafeCache(); // 10个线程并发写入 Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { final int threadId = i; threads[i] = new Thread(() -> { for (int j = 0; j < 1000; j++) { cache.put("key-" + threadId + "-" + j, "value-" + j); } }); } for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); System.out.println("缓存大小:" + cache.cache.size()); System.out.println("期望大小:10000"); } } /* 执行结果(多次运行): 第1次:缓存大小:9856 ❌ 数据丢失 第2次:缓存大小:9923 ❌ 数据丢失 第3次:程序卡死 ❌ 死循环(JDK 1.7) 第4次:抛出ConcurrentModificationException 问题列表: 1. 数据丢失(最常见) └─ 多个线程同时put,覆盖彼此的修改 2. 死循环(JDK 1.7的经典Bug) └─ 扩容时形成环形链表,get()陷入死循环 3. ConcurrentModificationException └─ 迭代时其他线程修改了HashMap 4. 数据不一致 └─ size()返回错误的值 真实案例: 某公司生产环境,使用HashMap作为本地缓存 高峰期CPU飙到100%,经排查是HashMap死循环 导致服务不可用,损失数百万 */ 解决方案演进 /** * 方案2:使用Hashtable(线程安全,但性能差) */ public class HashtableCache { private Map<String, String> cache = new Hashtable<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } } /* Hashtable的问题: 1. 整个方法都加synchronized(粗粒度锁) 2. 读写都加锁(读也需要互斥) 3. 性能差(高并发场景) 性能测试: 并发读写10000次 ├─ HashMap(不安全):50ms ├─ Hashtable(安全):500ms └─ 性能下降10倍! */ /** * 方案3:使用Collections.synchronizedMap(性能同样差) */ public class SynchronizedMapCache { private Map<String, String> cache = Collections.synchronizedMap(new HashMap<>()); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } } /* Collections.synchronizedMap的实现: public V put(K key, V value) { synchronized (mutex) { // 全局锁 return m.put(key, value); } } 问题:与Hashtable相同,粗粒度锁 */ /** * 方案4:使用ConcurrentHashMap(最佳方案) */ public class ConcurrentHashMapCache { private Map<String, String> cache = new ConcurrentHashMap<>(); public void put(String key, String value) { cache.put(key, value); } public String get(String key) { return cache.get(key); } public static void main(String[] args) throws InterruptedException { ConcurrentHashMapCache cache = new ConcurrentHashMapCache(); // 10个线程并发写入 Thread[] threads = new Thread[10]; for (int i = 0; i < 10; i++) { final int threadId = i; threads[i] = new Thread(() -> { for (int j = 0; j < 1000; j++) { cache.put("key-" + threadId + "-" + j, "value-" + j); } }); } long start = System.currentTimeMillis(); for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); long end = System.currentTimeMillis(); System.out.println("缓存大小:" + cache.cache.size()); System.out.println("期望大小:10000"); System.out.println("耗时:" + (end - start) + "ms"); } } /* 执行结果: 缓存大小:10000 ✅ 正确 期望大小:10000 耗时:65ms ConcurrentHashMap的优势: 1. 线程安全 2. 性能好(细粒度锁/无锁) 3. 读操作无锁(大部分情况) 4. 不会死循环 */ 性能对比总结 方案 线程安全 读性能 写性能 适用场景 HashMap ❌ 极快 极快 单线程 Hashtable ✅ 慢(加锁) 慢(加锁) 低并发 SynchronizedMap ✅ 慢(加锁) 慢(加锁) 低并发 ConcurrentHashMap ✅ 快(无锁) 较快(细粒度锁) 高并发 核心洞察:
...