同步包装器:让非线程安全的集合变安全
Collections.synchronizedXxx 是什么
Java 的集合大多数不是线程安全的。Collections 提供了包装方法,给集合「套上一层锁」:
java
// ❌ 非线程安全
List<String> list = new ArrayList<>();
// ✅ 线程安全:所有操作都被 synchronized 包裹
List<String> safe = Collections.synchronizedList(new ArrayList<>());
safe.add("item"); // 操作是原子的六种同步包装器
java
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, Int> syncMap = Collections.synchronizedMap(new HashMap<>());
SortedSet<String> syncSortedSet = Collections.synchronizedSortedSet(new TreeSet<>());
SortedMap<String, Int> syncSm = Collections.synchronizedSortedMap(new TreeMap<>());
Collection<String> syncColl = Collections.synchronizedCollection(new ArrayList<>());synchronizedMap 的原理
包装器给每个方法加了一把「内部锁」:
java
// Collections.synchronizedMap 的本质(简化)
public class SynchronizedMap<K, V> implements Map<K, V> {
private final Map<K, V> m; // 委托给原 Map
final Object mutex; // 锁对象
public V get(Object key) {
synchronized (mutex) {
return m.get(key);
}
}
public V put(K key, V value) {
synchronized (mutex) {
return m.put(key, value);
}
}
}所有操作共用一把锁,这就是性能差的根本原因。
迭代时必须额外同步
这是最容易出错的地方:
java
List<String> safe = Collections.synchronizedList(new ArrayList<>());
safe.addAll(Arrays.asList("a", "b", "c"));
// ❌ 危险:迭代时其他线程可能正在修改
for (String s : safe) {
System.out.println(s); // 可能抛 ConcurrentModificationException
}
// ✅ 正确:在 synchronized 块中迭代
synchronized (safe) {
for (String s : safe) {
System.out.println(s);
}
}Map 同样:
java
Map<String, Integer> safeMap = Collections.synchronizedMap(new HashMap<>());
// ✅ 正确:整个迭代过程都要锁住
synchronized (safeMap) {
for (Map.Entry<String, Integer> e : safeMap.entrySet()) {
System.out.println(e.getKey() + " = " + e.getValue());
}
}vs ConcurrentHashMap:什么时候用哪个
| 特性 | synchronizedMap | ConcurrentHashMap |
|---|---|---|
| 锁粒度 | 全局一把锁 | 分段锁/CAS |
| 并发读 | 写时阻塞读 | 无锁读 |
| 性能 | 差(高并发下) | 好 |
| 迭代安全 | 需额外同步 | 无需额外同步 |
java
// 10 线程各写入 10000 个元素:
// synchronizedMap: ~8000 ms
// ConcurrentHashMap: ~200 ms
// 差了 40 倍选择建议
java
// ✅ 高并发场景:ConcurrentHashMap
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// ✅ 低并发或简单场景:synchronizedMap
Map<String, Integer> simple = Collections.synchronizedMap(new HashMap<>());
// ✅ 需要保持顺序:Collections.synchronizedSortedMap
SortedMap<String, Integer> ordered = Collections.synchronizedSortedMap(new TreeMap<>());常见错误
错误 1:创建后忘记包起来
java
Map<String, Integer> map = new HashMap<>();
Map<String, Integer> safe = Collections.synchronizedMap(map);
// ❌ 直接用原 Map 仍然不安全
map.put("key", 1); // 非安全操作
// ✅ 统一使用包装后的引用
safe.put("key", 1);错误 2:迭代时没有同步
java
// ❌ 危险
for (String s : safeList) {
safeList.remove(s); // ConcurrentModificationException
}
// ✅ 正确
synchronized (safeList) {
Iterator<String> it = safeList.iterator();
while (it.hasNext()) {
if (shouldRemove(it.next())) {
it.remove(); // iterator.remove() 在锁内
}
}
}错误 3:用 ConcurrentHashMap 但调用了 synchronizedMap
java
ConcurrentHashMap<String, Integer> chm = new ConcurrentHashMap<>();
// ❌ 完全没必要,反而多了一层锁
synchronized (chm) {
chm.put("key", 1);
}总结
| 要点 | 说明 |
|---|---|
| 原理 | 每个方法包一层 synchronized |
| 迭代安全 | 必须在 synchronized 块中迭代 |
| 性能 | 差(全局锁),高并发用 ConcurrentHashMap |
| 适用场景 | 低并发、简单并发、遗留代码迁移 |
一句话:synchronizedMap 是「简单粗暴的线程安全」——功能正确,但性能差。生产环境高并发首选 ConcurrentHashMap。
