You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义线程安全HashMap实现的线程安全性及相关问题咨询

自定义SynchronizedHashMap相关问题解答

初始实现代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 06:57:24