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

多线程环境下同步FixedSizeLinkedHashMap超最大容量问题排查

问题描述

需要一个最大元素个数为10000的映射,插入新记录时自动移除最旧条目,用于多线程场景。但实际运行中发现元素数量超过10000(甚至达到20000+),以下是实现代码:

public class FixedSizeLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;

    public FixedSizeLinkedHashMap(int maxSize) {
        super(maxSize, 0.75f, true);
        this.maxSize = maxSize;
    }

    @Override
    protected synchronized boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxSize;
    }

    @Override
    public synchronized V put(K key, V value) {
        return super.put(key, value);
    }

    @Override
    public synchronized V get(Object key) {
        return super.get(key);
    }

    @Override
    public synchronized V remove(Object key) {
        return super.remove(key);
    }

    @Override
    public synchronized void clear() {
        super.clear();
    }
}

调用代码:

fixedsizeLhm = new FixedSizeLinkedHashMap<>(10000);
问题原因与修复方案

核心问题1:初始容量参数理解错误

LinkedHashMap构造函数的第一个参数是底层数组的初始容量,不是你要限制的最大元素个数。你现在传入10000,结合0.75的负载因子,扩容阈值是10000 * 0.75 = 7500——当元素个数达到7500时,HashMap就会自动扩容,底层数组容量直接翻倍到20000。这就是你看到“容量超过10000”的原因(实际是底层数组容量,而非元素个数)。

修复方式:根据最大元素个数和负载因子计算初始容量,确保扩容阈值大于等于maxSize,避免提前扩容。修改构造函数:

public FixedSizeLinkedHashMap(int maxSize) {
    // 计算初始容量:确保扩容阈值 >= maxSize
    int initialCapacity = (int) Math.ceil(maxSize / 0.75f) + 1;
    super(initialCapacity, 0.75f, true);
    this.maxSize = maxSize;
}

核心问题2:未覆盖所有线程不安全方法

你只给put、get等少数方法加了synchronized,但LinkedHashMap还有putAll、containsKey、containsValue、entrySet等方法没有同步。多线程下调用这些未同步的方法,会导致并发冲突,可能出现元素个数超过maxSize的情况。

修复方式:覆盖所有可能被调用的方法,添加synchronized修饰,比如:

@Override
public synchronized void putAll(Map<? extends K, ? extends V> m) {
    super.putAll(m);
}

@Override
public synchronized boolean containsKey(Object key) {
    return super.containsKey(key);
}

@Override
public synchronized boolean containsValue(Object value) {
    return super.containsValue(value);
}

// 其他如entrySet、keySet、values等方法也建议同步,避免迭代时的并发修改问题
@Override
public synchronized Set<Map.Entry<K, V>> entrySet() {
    return Collections.unmodifiableSet(super.entrySet());
}

次要问题:removeEldestEntry的同步多余

put方法已经加了synchronized,removeEldestEntry是在put的同步块内被调用的,不需要额外给它加synchronized修饰,直接去掉即可:

@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    return size() > maxSize;
}

额外建议:考虑使用ConcurrentLinkedHashMap

如果是高并发场景,自己实现同步的LinkedHashMap性能可能不够理想,可以考虑使用com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru库,它是专门为并发场景设计的LRU缓存实现,不需要自己处理同步问题。


内容的提问来源于stack exchange,提问作者user3201343

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:40:55