Java HashMap中的线程问题
发生了一些我不确定是否可能发生的事情。显然是的,因为我已经看到了,但我需要找到根本原因&我希望你们都能帮忙 我们有一个系统可以查找zipcode的纬度和经度。我们不是每次都访问它,而是将结果缓存在廉价的内存哈希表缓存中,因为邮政编码的lat&long变化的频率往往比我们发布的要低 无论如何,哈希由一个类包围,该类有一个同步的“get”和“add”方法。我们以单例访问这个类 我并不是说这是最好的设置,但这正是我们所处的位置。(我计划更改为尽快在Collections.synchronizedMap()调用中包装映射。) 我们在多线程环境中使用这个缓存,在多线程环境中,我们线程2调用2个zip(因此我们可以计算两者之间的距离)。这些调用有时几乎同时发生,因此很可能两个调用同时访问映射 就在最近,我们遇到了一个事件,两个不同的邮政编码返回相同的值。假设初始值实际上是不同的,那么将值写入映射是否会导致为两个不同的键写入相同的值?或者,2“get”是否有任何方式可以交叉连接并意外返回相同的值 我唯一的另一种解释是,初始数据已损坏(错误的值),但这似乎不太可能 任何想法都将不胜感激。 谢谢 彼得 (注:如果您需要更多信息、代码等,请告诉我。)Java HashMap中的线程问题,java,multithreading,collections,concurrency,hashmap,Java,Multithreading,Collections,Concurrency,Hashmap,发生了一些我不确定是否可能发生的事情。显然是的,因为我已经看到了,但我需要找到根本原因&我希望你们都能帮忙 我们有一个系统可以查找zipcode的纬度和经度。我们不是每次都访问它,而是将结果缓存在廉价的内存哈希表缓存中,因为邮政编码的lat&long变化的频率往往比我们发布的要低 无论如何,哈希由一个类包围,该类有一个同步的“get”和“add”方法。我们以单例访问这个类 我并不是说这是最好的设置,但这正是我们所处的位置。(我计划更改为尽快在Collections.synchronizedMap
为什么会这样很难说。更多的代码可能会有所帮助
无论如何,您应该只使用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));
}