代码之家  ›  专栏  ›  技术社区  ›  SSpoke

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

  •  3
  • SSpoke  · 技术社区  · 10 年前

    错误如下所示

    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;
        }
    

    我想制作一个自动删除旧条目的Map,所以我使用了这个片段

    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);
    

    显然,这是一种与多个线程一起使用时发生的CocurrentModificationException。。但为什么它会在内部崩溃,有人知道我如何将它与CocurrentHashMap一起使用吗?我正在努力解决这个问题,而不只是在整个问题上做尝试 获取最后完成的命令 作用

    我想要一个Map,它可以清除旧垃圾,但仍然保存至少5000个键/值条目。

    2 回复  |  直到 10 年前
        1
  •  5
  •   Keerthivasan    10 年前

    基于堆栈跟踪,我假设代码试图从索引中移除值,该索引的项已被另一个线程移除。这让它很容易扔 NPE 访问的财产时 null 参考也许,您应该尝试同步集合

    根据以下文件 LinkedHashMap

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

       Map m = Collections.synchronizedMap(new LinkedHashMap(...));
    
        2
  •  1
  •   Seelenvirtuose    10 年前

    您说过多个线程正在访问此映射。这确实会导致 remove 操作 LinkedHashMap.Entry 例子这是该方法的实现:

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

    在这里 before 和之后 refer 链接到当前条目的链接前置项和后续项。如果另一个线程已经更改了条目之间的链接,这当然会导致意外的行为,例如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等)。