带有removeEldestEntry的Java LinkedHashMap会导致java.lang.NullPointerException

时间:2014-04-01 07:03:01

标签: java map get linkedhashmap lru

错误看起来像这样

Exception in thread "Thread-1" java.lang.NullPointerException
    at java.util.LinkedHashMap$Entry.remove(LinkedHashMap.java:332)
    at java.util.LinkedHashMap$Entry.recordAccess(LinkedHashMap.java:356)
    at java.util.LinkedHashMap.get(LinkedHashMap.java:304)
    at Server.getLastFinishedCommands(Server.java:9086)
    at Server.processPacket(Server.java:484)
    at PacketWorker.run(PacketWorker.java:34)
    at java.lang.Thread.run(Thread.java:744)

getLastFinishedCommands内,我使用

   public List<CCommand> getLastFinishedCommands(UserProfile player) {
        List<CCommand> returnList = new ArrayList<CCommand>();

        if(!finishedCommands.containsKey(player.myWebsitecmd-1)) {
            getSavedState(player);
            return null;
        }

        try { //<-- added this try/catch so it doesn't happen again.
            //Get commands.
            CCommand cmd;
            long i;
            long startIndex = player.myWebsitecmd;
            long endIndex = startIndex+LIMIT_COMMANDS;

            for(i = startIndex; i <= endIndex; i++) {
                cmd = finishedCommands.get(i);   //<-- this is line 9086
                if(cmd == null) {
                    return returnList;
                }
                returnList.add(cmd);
            }
        } catch(Exception e) {} //<-- added this try/catch so it doesn't happen again.
        return returnList;
    }

我想创建一个自动删除旧条目的地图,因此我使用了这个代码段

public static <K, V> Map<K, V> createLRUMap(final int maxEntries) {
    return new LinkedHashMap<K, V>(maxEntries*3/2, 0.7f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > maxEntries;
        }
    };
}

像这样使用它

public static int final MAX_COMMANDS_QUEUE = 5000;
public Map<Long, CCommand> finishedCommands = createLRUMap(MAX_COMMANDS_QUEUE);

显然,当使用多个线程时会出现某种CocurrentModifcationException ..但为什么它会在内部崩溃,任何人都知道如何像CocurrentHashMap一样使用它?我试图解决这个问题,而不是仅仅尝试在整个getLastFinishedCommands函数中使用try / catch。

我想要一张从旧垃圾中清除自己的地图,但仍然保留至少5000个键/值条目。

2 个答案:

答案 0 :(得分:4)

基于堆栈跟踪,我假设代码尝试从其项已被另一个线程删除的索引中删除该值。这使得它在访问NPE引用的属性时抛出null。也许你应该尝试同步集合

来自LinkedHashMap

的文档
  

请注意,此实现未同步。如果多个线程同时访问链接的哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部进行同步。这通常通过在自然封装地图的某个对象上进行同步来实现。如果不存在这样的对象,那么地图应该被&#34;包裹&#34;使用Collections.synchronizedMap方法。这最好在创建时完成,以防止意外地不同步访问地图:

   Map m = Collections.synchronizedMap(new LinkedHashMap(...));

答案 1 :(得分:1)

你说多个线程正在访问这个地图。这确实可能导致remove实例的LinkedHashMap.Entry操作中的NPE。这是此方法的实现:

private void remove() {
    before.after = after;
    after.before = before;
}

此处beforerefer之后是当前条目的链接前任和后继者。如果另一个线程已经改变了条目之间的链接,这当然会导致意外的行为,例如NPE。

解决方案是 - 您猜对了 - 将生成的地图包装在同步地图中。如:

public static <K, V> Map<K, V> createLRUMap(final int maxEntries) {
    Map<K,V> result = new LinkedHashMap<K, V>(maxEntries*3/2, 0.7f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > maxEntries;
        }
    };
    return Collections.synchronizedMap(result);
}

这个同步包装器确实会同步所有对底层映射的调用,因此只允许一个线程通过每个方法(例如get,put,contains,size等)。