2008-10-28 62 views
3

发生了一些我不确定应该可能发生的事情。很明显,因为我已经看到了,但我需要找到根本原因&我希望你们都能帮上忙。Java HashMap中的线程问题

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

不管怎么说,哈希是由具有“get”和“添加”方法都同步类包围。我们以单身人士的身份访问此课程。

我不是说这是最好的设置,但它也正是我们在。 (我打算改变以包裹在一个Collections.synchronizedMap()调用地图)尽快调用

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

就在最近我们遇到了一个事件,两个不同的邮政编码返回相同的值。假设初始值实际上是不同的,有没有办法将值写入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; 
} 
} 
+0

我没有看到任何使用 “实例”。它有什么用途?另外,如果你创建了缓存“Map ”,那么你将会更清楚你正在做什么。 – 2008-10-28 19:34:36

+0

我会仔细检查InMemoryGeocodingCache.add的任何调用。 – 2008-10-28 19:36:48

+0

如果LatLongPair是真正不可变的(没有setter),你应该使lat和lon最终。从安全发布/ Java内存模型的角度来看,这从并发的角度来看是有意义的。 – 2008-10-29 05:15:08

回答

6

为什么它的发生是很难说。更多的代码可以帮助。

无论如何,您应该只是使用ConcurrentHashMap。总的来说,这比同步的Map更高效。你不会同步访问它,它会在内部处理它(比你更有效)。

4

一件事看出来的是,如果该键或值可能会发生变化,例如,如果,而不是做一个新的对象每次插入,你只是改变现有对象和值重新插入它。

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

+0

他使用字符串作为密钥 - 不需要担心哈希代码 – 2008-10-28 21:42:51

3

是否有可能LatLonPair被修改?我建议让lat和lon字段最终确定,以便它们不会在代码中的其他地方被意外修改。

笔记,你也应该让你单身“实例”和绘图参照“缓存”决赛。

8

该代码看起来正确。

唯一担心的是,纬度和经度是包可见的,所以下面可能为同一封装代码:

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

这显然会改变在缓存对象。

因此,也是最后的决定。

P.S.由于您的密钥(zip代码)是独一无二的,因此不需要为每个操作计算散列值。使用TreeMap更容易(包装到Collections.synchronizedMap()中)。

P.P.S.实践方法:为两个线程编写一个测试,在无限循环中执行put/get操作,验证每个get的结果。你需要一个多CPU机器。

2

James是对的。既然你传递一个Object,它的内部可以被修改,任何持有该Object(Map)引用的东西都会反映这个变化。最后是一个很好的答案。

0

我真的没有发现你发布的代码会导致你所描述的问题。我的猜测是,您的地理代码缓存的客户端存在问题。

其他的事情要考虑(其中一些是非常明显的,但我想我会指出这些问题反正):

  1. 哪两个邮政编码是你有问题?您确定它们在源系统中没有相同的地理编码吗?
  2. 你确定你没有意外地比较两个相同的邮政编码吗?
0

的存在有(字符串ZIP)方法意味着你有什么样的代码如下:

GeocodingCache cache = InMemoryGeocodingCache.getInstance(); 

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

很不幸,这可能让你同步问题之间的有()返回false和添加()添加哪个可能会导致您所描述的问题

更好的解决方案将是移动add方法里面的检查,以便检查和更新由同一个锁状覆盖:

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

我要提到的另一件事是,如果你使用的是getInstance()作为一个单例,你应该有一个私有构造函数来阻止使用新的InMemoryGeocodingCache()创建额外缓存的可能性。

0

这里是一个HashMap中的Java文档:

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

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

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

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