Skip to content

同步包装器:让非线程安全的集合变安全

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:什么时候用哪个

特性synchronizedMapConcurrentHashMap
锁粒度全局一把锁分段锁/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。


相关链接

基于 VitePress 构建