3

发生了一些我不确定是否可能发生的事情。显然是的,因为我已经看到了,但是我需要找到根本原因,希望大家能提供帮助。

我们有一个系统可以查找邮政编码的纬度和经度。我们不是每次都访问它,而是将结果缓存在一个廉价的内存中 HashTable 缓存中,因为邮政编码的纬度和经度往往比我们发布的更频繁。

无论如何,散列被一个类包围,该类具有同步的“get”和“add”方法。我们作为单例访问这个类。

我并不是说这是最好的设置,但它就是我们所处的位置。(我计划尽快更改以将 Map 包装在 Collections.synchronizedMap() 调用中。)

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

就在最近,我们发生了一个事件,两个不同的邮政编码返回相同的值。假设初始值实际上不同,有没有办法将值写入 Map 会导致为两个不同的键写入相同的值?或者,有什么方法可以让 2 个“获取”跨线并意外返回相同的值?

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

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

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

public class InMemoryGeocodingCache implements GeocodingCache
{

private Map cache = new HashMap();
private static GeocodingCache instance = new InMemoryGeocodingCache();

public static GeocodingCache getInstance()
{
    return instance;
}

public synchronized LatLongPair get(String zip)
{
    return (LatLongPair) cache.get(zip);
}

public synchronized boolean has(String zip)
{
    return cache.containsKey(zip);
}

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


public class LatLongPair {
double lat;
double lon;

LatLongPair(double lat, double lon)
{
    this.lat = lat;
    this.lon = lon;
}

public double getLatitude()
{
    return this.lat;
}

public double getLongitude()
{
    return this.lon;
}
}
4

8 回答 8

8

代码看起来正确。

唯一需要担心的是 lat 和 lon 是包可见的,因此对于相同的包代码,以下是可能的:

LatLongPair llp = InMemoryGeocodingCache.getInstance().get(ZIP1);
llp.lat = x;
llp.lon = y;

这显然会修改缓存中的对象。

因此,也使 lat 和 lon 最终确定。

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

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

于 2008-10-28T20:48:56.823 回答
6

为什么会发生这种情况很难说。更多代码可能会有所帮助。

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

于 2008-10-28T18:39:59.970 回答
4

需要注意的一件事是键或值是否可能发生变化,例如,如果不是为每次插入创建一个新对象,您只是更改现有对象的值并重新插入它。

您还需要确保 key 对象同时定义 hashCode 和 equals 以不违反 HashMap 协定(即,如果 equals 返回 true,则 hashCodes 需要相同,但不一定反之亦然)。

于 2008-10-28T18:47:13.137 回答
3

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

请注意,您还应该使您的单例“实例”和地图参考“缓存”最终。

于 2008-10-28T20:10:51.863 回答
2

詹姆斯是正确的。由于您要交回一个对象,因此可以修改其内部结构,并且任何持有对该对象(映射)的引用的东西都将反映该更改。最终是一个很好的答案。

于 2008-10-28T20:52:52.200 回答
0

这是 HashMap 上的 java 文档:

http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html

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

地图 m = Collections.synchronizedMap(new HashMap(...));

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

于 2013-11-14T16:34:25.727 回答
0

The presence of the has(String ZIP) method implies that you have something like the following in your code:

GeocodingCache cache = InMemoryGeocodingCache.getInstance();

if (!cache.has(ZIP)) {
    cache.add(ZIP, x, y);
}

Unfortunately this opens you up to sync problems between the has() returning false and the add() adding which could result in the issue you described.

A better solution would be to move the check inside the add method so the check and update are covered by the same lock like:

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

The other thing I should mention is that if you are using getInstance() as a singleton you should have a private constructor to stop the possibility of additional caches being created using new InMemoryGeocodingCache().

于 2008-11-06T11:38:54.837 回答
0

我真的看不出您发布的代码有什么问题会导致您描述的问题。我的猜测是你的地理代码缓存的客户端有问题。

其他需要考虑的事情(其中一些非常明显,但我想我还是会指出它们):

  1. 您遇到了哪两个邮政编码问题?您确定它们在源系统中没有相同的地理编码吗?
  2. 你确定你不会不小心比较两个相同的邮政编码吗?
于 2008-10-29T02:20:59.587 回答