Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java HashMap中的线程问题_Java_Multithreading_Collections_Concurrency_Hashmap - Fatal编程技术网

Java HashMap中的线程问题

Java HashMap中的线程问题,java,multithreading,collections,concurrency,hashmap,Java,Multithreading,Collections,Concurrency,Hashmap,发生了一些我不确定是否可能发生的事情。显然是的,因为我已经看到了,但我需要找到根本原因&我希望你们都能帮忙 我们有一个系统可以查找zipcode的纬度和经度。我们不是每次都访问它,而是将结果缓存在廉价的内存哈希表缓存中,因为邮政编码的lat&long变化的频率往往比我们发布的要低 无论如何,哈希由一个类包围,该类有一个同步的“get”和“add”方法。我们以单例访问这个类 我并不是说这是最好的设置,但这正是我们所处的位置。(我计划更改为尽快在Collections.synchronizedMap

发生了一些我不确定是否可能发生的事情。显然是的,因为我已经看到了,但我需要找到根本原因&我希望你们都能帮忙

我们有一个系统可以查找zipcode的纬度和经度。我们不是每次都访问它,而是将结果缓存在廉价的内存哈希表缓存中,因为邮政编码的lat&long变化的频率往往比我们发布的要低

无论如何,哈希由一个类包围,该类有一个同步的“get”和“add”方法。我们以单例访问这个类

我并不是说这是最好的设置,但这正是我们所处的位置。(我计划更改为尽快在Collections.synchronizedMap()调用中包装映射。)

我们在多线程环境中使用这个缓存,在多线程环境中,我们线程2调用2个zip(因此我们可以计算两者之间的距离)。这些调用有时几乎同时发生,因此很可能两个调用同时访问映射

就在最近,我们遇到了一个事件,两个不同的邮政编码返回相同的值。假设初始值实际上是不同的,那么将值写入映射是否会导致为两个不同的键写入相同的值?或者,2“get”是否有任何方式可以交叉连接并意外返回相同的值

我唯一的另一种解释是,初始数据已损坏(错误的值),但这似乎不太可能

任何想法都将不胜感激。 谢谢 彼得

(注:如果您需要更多信息、代码等,请告诉我。)


为什么会这样很难说。更多的代码可能会有所帮助


无论如何,您应该只使用ConcurrentHashMap。一般来说,这将比同步映射更有效。您不同步对它的访问,它在内部处理它(比您可以更有效)。

需要注意的一件事是,键或值是否可能正在更改,例如,如果不是为每次插入创建新对象,而是更改现有对象的值并重新插入它


您还需要确保key对象定义hashCode和equals的方式不会违反HashMap约定(即如果equals返回true,则hashCodes必须相同,但不一定相反)。

是否可能修改LatLonPair?我建议将lat和lon字段设置为final,这样它们就不会在代码的其他地方被意外修改


注意,您还应该将您的单例“实例”和映射引用“缓存”设置为最终版本。

代码看起来是正确的

唯一的问题是lat和lon是包可见的,因此对于相同的包代码,可能存在以下情况:

LatLongPair llp = InMemoryGeocodingCache.getInstance().get(ZIP1);
llp.lat = x;
llp.lon = y;
这显然会修改缓存中的对象

因此,也要使lat和lon成为最终版本

另外,由于您的密钥(邮政编码)是唯一且小的,因此无需在每次操作中计算哈希值。使用TreeMap(包装到Collections.synchronizedMap()中)更容易


p.p.S.实用方法:为在永无止境的循环中执行put/get操作的两个线程编写一个测试,在每个get上验证结果。不过,您需要一台多CPU机器。

詹姆斯是正确的。因为您要返回一个对象,所以它的内部可以被修改,任何包含对该对象(映射)的引用的东西都会反映出这种变化。Final是一个很好的答案。

我并不认为您发布的代码有任何问题会导致您描述的问题。我的猜测是,您的地理代码缓存的客户端有问题

其他的事情要考虑(有些是很明显的,但我想我会指出它们):

  • 哪两个邮政编码有问题?你确定他们在源系统中没有相同的地理代码吗
  • 你确定你不是无意中比较了两个相同的邮政编码吗

  • has(String ZIP)方法的存在意味着代码中有如下内容:

    GeocodingCache cache = InMemoryGeocodingCache.getInstance();
    
    if (!cache.has(ZIP)) {
        cache.add(ZIP, x, y);
    }
    
    不幸的是,这会导致has()返回false和add()添加之间出现同步问题,这可能导致您描述的问题

    更好的解决方案是将检查移动到add方法中,以便检查和更新由相同的锁覆盖,如:

    public synchronized void add(String zip, double lat, double lon) {
        if (cache.containsKey(zip)) return;
        cache.put(zip, new LatLongPair(lat, lon));
    }
    

    我应该提到的另一件事是,如果您使用getInstance()作为单例,那么您应该有一个私有构造函数来阻止使用new InMemoryGeocodingCache()

    创建额外缓存的可能性,这是HashMap上的java文档:

    请注意,此实现是不同步的。如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了该映射,则必须在外部对其进行同步。(结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。)这通常通过在自然封装映射的某个对象上进行同步来实现。如果不存在此类对象,则应使用Collections.synchronizedMap方法“包装”映射。最好在创建时执行此操作,以防止意外不同步地访问映射:

    Map m=Collections.synchronizedMap(新的HashMap(…)


    或者更好,使用java.util.concurrent.ConcurrentHashMap

    我没有看到任何使用“实例”的东西。那是干什么用的?另外,如果您将缓存设置为“映射”,您所做的事情就会更清楚。我会非常仔细地检查MemoryGeocodingCache.add中的任何调用。如果LatLongPair是真正不可变的(没有设置器),那么您应该将lat和lon设置为final。从安全发布/java内存模型的并发性角度来看,这是有意义的
    public synchronized void add(String zip, double lat, double lon) {
        if (cache.containsKey(zip)) return;
        cache.put(zip, new LatLongPair(lat, lon));
    }