Collections.synchronizedMap() 和 ConcurrentHashMap 都是Java中用于线程安全的Map实现,但它们在设计理念、性能和特性上有显著区别:
1. 锁的粒度
Collections.synchronizedMap()
- 全表锁:对整个Map对象加锁
- 任何线程访问任何方法时都会锁定整个Map
- 读操作和写操作都会阻塞其他所有操作
ConcurrentHashMap
- 分段锁 (Java 7)或 CAS + synchronized(Java 8)
- 只锁定部分数据(桶或节点)
- 允许多个线程同时进行读操作
- 允许不同线程同时修改不同的段/节点
2. 并发性能对比
java
复制代码
// synchronizedMap - 低并发性能
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// ConcurrentHashMap - 高并发性能
ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>();
| 场景 |
synchronizedMap |
ConcurrentHashMap |
| 读多写少 |
差(所有读操作串行) |
优秀(读操作无锁) |
| 写多读少 |
差(所有写操作串行) |
良好(部分写可并行) |
| 高并发 |
性能急剧下降 |
性能保持相对稳定 |
3. 迭代器行为
synchronizedMap
- 强一致性迭代器
- 迭代过程中会持有锁,阻止其他线程修改
- 迭代时看到的是某个时间点的快照
java
复制代码
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
// 迭代时需要手动同步
synchronized(syncMap) {
for(String key : syncMap.keySet()) {
// 线程安全
}
}
ConcurrentHashMap
- 弱一致性迭代器
- 迭代时不需要锁,不阻止其他线程修改
- 可能反映迭代过程中的修改,也可能不反映
java
复制代码
ConcurrentHashMap<String, String> concurrentMap = new ConcurrentHashMap<>();
// 迭代不需要额外同步
for(String key : concurrentMap.keySet()) {
// 可能看到其他线程的修改
}
4. 空值支持
| 特性 |
synchronizedMap |
ConcurrentHashMap |
| key为null |
允许(取决于底层Map) |
不允许 |
| value为null |
允许(取决于底层Map) |
不允许 |
5. 内存可见性保证
synchronizedMap
- 通过
synchronized关键字保证可见性
- 遵循
synchronized的happens-before规则
ConcurrentHashMap
- 使用
volatile变量保证可见性
- 遵循JMM(Java内存模型)的happens-before规则
6. 原子操作支持
synchronizedMap
java
复制代码
// 手动实现putIfAbsent
synchronized(syncMap) {
if(!syncMap.containsKey(key)) {
syncMap.put(key, value);
}
}
ConcurrentHashMap
java
复制代码
// 原子操作,无需外部同步
concurrentMap.putIfAbsent(key, value);
concurrentMap.compute(key, (k, v) -> v == null ? 1 : v + 1);
concurrentMap.merge(key, 1, Integer::sum);
7. 推荐使用场景
使用 Collections.synchronizedMap() 当:
- 并发量非常低
- 需要保持与现有代码的兼容性
- 需要允许null值
- 对性能要求不高的小规模应用
使用 ConcurrentHashMap 当:
- 高并发环境
- 读多写少的场景
- 需要更好的可伸缩性
- 需要使用原子操作
- 现代Java应用(Java 5+)
8. 代码示例对比
java
复制代码
// 方式1: synchronizedMap
public class SyncMapExample {
private Map<String, Integer> map =
Collections.synchronizedMap(new HashMap<>());
public void increment(String key) {
synchronized(map) { // 需要额外同步
map.put(key, map.getOrDefault(key, 0) + 1);
}
}
}
// 方式2: ConcurrentHashMap
public class ConcurrentMapExample {
private ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();
public void increment(String key) {
map.compute(key, (k, v) -> v == null ? 1 : v + 1);
// 或者使用原子操作
// map.merge(key, 1, Integer::sum);
}
}
总结表格
| 特性 |
Collections.synchronizedMap() |
ConcurrentHashMap |
| 锁机制 |
全表锁 |
分段锁/CAS+synchronized |
| 并发性能 |
低 |
高 |
| 迭代器 |
强一致性,需要锁 |
弱一致性,无需锁 |
| 空值 |
允许 |
不允许 |
| 原子操作 |
需要手动同步 |
内置支持 |
| 内存开销 |
较低 |
较高(分段结构) |
| 适用场景 |
低并发、简单场景 |
高并发、复杂场景 |
建议 :在现代Java并发编程中,优先考虑使用ConcurrentHashMap,除非有特殊需求(如需要null值)或运行在低版本Java环境中。