自定义线程安全HashMap实现的线程安全性及相关问题咨询
初始实现代码
public final class SynchronizedHashMap<K, V> extends HashMap<K, V> { private final Object lock = new Object(); public SynchronizedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); } public SynchronizedHashMap(int initialCapacity) { super(initialCapacity); } public SynchronizedHashMap() { super(); } public SynchronizedHashMap(Map<? extends K, ? extends V> m) { super(m); } private void sync(Runnable runnable) { synchronized (lock) { runnable.run(); } } private <T> void sync(Consumer<T> consumer, T t) { synchronized (lock) { consumer.accept(t); } } private <T> T sync(Supplier<T> supplier) { synchronized (lock) { return supplier.get(); } } private <T, R> R sync(Function<T, R> function, T t) { synchronized (lock) { return function.apply(t); } } private <T, U, R> R sync(BiFunction<T, U, R> function, T t, U u) { synchronized (lock) { return function.apply(t, u); } } @Override public int size() { return sync(super::size); } @Override public boolean isEmpty() { return sync(super::isEmpty); } @Override public V get(Object key) { return sync(super::get, key); } @Override public boolean containsKey(Object key) { return sync(super::containsKey, key); } @Override public V put(K key, V value) { return sync(super::put, key, value); } @Override public void putAll(Map<? extends K, ? extends V> m) { sync(super::putAll, m); } @Override public V remove(Object key) { synchronized (lock) { return super.remove(key); } } @Override public void clear() { sync(super::clear); } @Override public boolean containsValue(Object value) { return sync(super::containsValue, value); } @Override public Set<K> keySet() { return sync(super::keySet); } @Override public Collection<V> values() { return sync(super::values); } @Override public Set<Entry<K, V>> entrySet() { return sync(super::entrySet); } @Override public V getOrDefault(Object key, V defaultValue) { return sync(super::getOrDefault, key, defaultValue); } @Override public V putIfAbsent(K key, V value) { return sync(super::putIfAbsent, key, value); } @Override public boolean remove(Object key, Object value) { return sync(super::remove, key, value); } @Override public boolean replace(K key, V oldValue, V newValue) { synchronized (lock) { return super.replace(key, oldValue, newValue); } } @Override public V replace(K key, V value) { return sync(super::replace, key, value); } @Override public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { return sync(super::computeIfAbsent, key, mappingFunction); } @Override public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { return sync(super::computeIfPresent, key, remappingFunction); } @Override public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) { return sync(super::compute, key, remappingFunction); } @Override public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) { synchronized (lock) { return super.merge(key, value, remappingFunction); } } @Override public void forEach(BiConsumer<? super K, ? super V> action) { sync(super::forEach, action); } @Override public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) { sync(super::replaceAll, function); } @Override public Object clone() { return sync(super::clone); } @Override public boolean equals(Object o) { return sync(super::equals, o); } @Override public int hashCode() { return sync(super::hashCode); } @Override public String toString() { return sync(super::toString); } }
初始问题答复
1. 该类是否属于线程安全类?
原始实现不属于完全线程安全的类,核心问题如下:
- 最严重的缺陷是
keySet()、values()、entrySet()三个方法直接返回HashMap原生的视图集合,这些视图的遍历、增删操作没有和Map的其他操作共享同一把锁,多线程并发场景下,一个线程遍历视图、另一个线程修改Map结构时,会直接触发ConcurrentModificationException,极端情况下甚至会触发HashMap并发扩容导致的死循环、数据错乱问题。 - 你重写的所有Map直接操作方法确实通过统一加锁实现了互斥,但没有覆盖视图操作的同步逻辑。另外
clone()方法返回的实际是普通HashMap实例,没有包装为加锁的SynchronizedHashMap,如果调用方拿到返回值后直接并发操作,会有线程安全风险,但这属于使用层面的坑,不属于类本身的方法同步遗漏。 - 额外风险:因为你采用继承而非组合的方式实现,若后续JDK版本为HashMap新增公开的修改方法,你的类没有同步重写的话,会直接出现锁逃逸的线程安全问题,这也是JDK不推荐用继承实现同步容器的核心原因。
2. 是否可以将该类称为HashMap的包装类?
不能。
包装类是基于装饰器模式实现,核心特征是持有被包装对象的实例作为私有成员,所有功能通过委托调用被包装实例的方法实现,和被包装类是组合关系。而你的实现是直接继承HashMap,通过super关键字调用父类实现,属于继承层面的功能扩展,不是包装类。JDK中Collections.synchronizedMap返回的同步Map才是典型的包装类实现,内部持有被装饰的Map实例和全局锁,不继承具体Map实现类。
3. 如何编写单元测试验证线程安全性
线程安全测试无法做到100%覆盖所有并发场景,但可以通过以下几类测试尽可能发现问题:
- 并发写正确性测试:用
CountDownLatch控制多线程同时启动,比如启动100个线程,每个线程写入1000个唯一不重复的key-value,所有线程执行完成后,校验Map的size等于总写入key数,每个key对应的value和写入值完全一致,无数据丢失、错误覆盖。 - 并发读写稳定性测试:启动等量的读、写线程,写线程持续随机执行put、remove、clear操作,读线程持续随机执行get、containsKey、遍历视图操作,持续运行10~30秒,整个过程中不能抛出任何异常(包括ConcurrentModificationException、数组越界等并发问题导致的异常)。
- 复合操作原子性测试:针对
putIfAbsent、computeIfAbsent、replace(k,v,v)这类复合操作,启动多线程同时对同一个key执行操作,比如多线程同时调用computeIfAbsent写入同一个key,校验映射函数仅执行一次,最终值符合预期,不会出现重复计算、值覆盖错误。 - 视图操作并发测试:单独获取keySet、values、entrySet实例,多线程同时对视图执行遍历、增删元素操作,校验无异常抛出,操作结果符合预期。
更新问题:视图方法修改后的有效性
你修改后的视图相关代码如下:
@Override public Set<K> keySet() { return sync(this::synchronizedKeySet); } private Set<K> synchronizedKeySet() { return Collections.synchronizedSet(super.keySet()); } @Override public Collection<V> values() { return sync(this::synchronizedValues); } private Collection<V> synchronizedValues() { return Collections.synchronizedCollection(super.values()); } @Override public Set<Entry<K, V>> entrySet() { return sync(this::synchronizedEntrySet); } private Set<Entry<K, V>> synchronizedEntrySet() { return Collections.synchronizedSet(super.entrySet()); }
上述修改没有解决三个视图的线程安全问题,核心原因是锁不共享:Collections.synchronizedSet、Collections.synchronizedCollection返回的同步集合,默认用集合自身实例作为锁对象,和你SynchronizedHashMap里定义的lock全局锁完全是两个独立的锁,两者不互斥。举个典型场景:线程A获取keySet后开始遍历,遍历过程中会持有synchronizedSet自身的锁;线程B此时调用Map的put方法,只需要获取你定义的lock锁即可执行,不需要等待keySet的锁释放,两个线程可以同时执行遍历和结构修改操作,依然会触发HashMap的modCount校验,抛出ConcurrentModificationException,和修改前的问题没有本质区别。
正确的实现方式可以参考JDK Collections.SynchronizedMap的源码:自定义视图的内部类,所有视图的方法都统一持有外部Map的lock对象加锁,保证视图操作和Map其他操作完全互斥。另外JDK实现会缓存第一次生成的视图实例,多次调用keySet等方法返回同一个对象,避免重复创建包装对象的性能损耗。
内容的提问来源于stack exchange,提问作者Javid

