[英]Threading issues in a Java HashMap
發生了一些我不確定應該可行的事情。 顯然它是,因為我已經看過了,但我需要找到根本原因而且我希望你們都能提供幫助。
我們有一個查找緯度和經度的郵政編碼系統。 我們不是每次都訪問它,而是將結果緩存在廉價的內存中HashTable緩存中,因為郵政編碼的緯度和長度往往比我們發布的更少。
無論如何,哈希被一個具有“get”和“add”方法的類所包圍,這兩個方法都是同步的。 我們以單身形式訪問此類。
我並不是說這是最好的設置,但它就是我們所處的位置。 (我計划更改為盡快將Map包裝在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;
}
}
代碼看起來正確。
唯一的問題是lat和lon是包可見的,因此對於相同的包代碼可以使用以下內容:
LatLongPair llp = InMemoryGeocodingCache.getInstance().get(ZIP1);
llp.lat = x;
llp.lon = y;
這顯然會修改緩存中的對象。
所以也讓lat和lon決賽。
PS由於您的密鑰(zip-code)是唯一且小的,因此無需在每個操作上計算哈希值。 使用TreeMap(包裝到Collections.synchronizedMap()中)更容易。
PPS實用方法:為兩個線程編寫測試,在永不停止的循環中執行put / get操作,在每次獲取時驗證結果。 你需要一台多CPU機器。
為什么會發生這種情況很難說。 更多代碼可以幫助。
你應該只是使用ConcurrentHashMap。 一般來說,這比同步Map更有效。 您不同步對它的訪問,它在內部處理它(比您更有效)。
要注意的一件事是,如果鍵或值可能正在改變,例如,如果不是為每個插入創建一個新對象,那么您只需更改現有對象的值並重新插入即可。
您還需要確保密鑰對象以不違反HashMap契約的方式定義hashCode和equals(即如果equals返回true,則hashCodes需要相同,但不一定相反)。
是否有可能修改LatLonPair? 我建議將lat和lon字段設為final,這樣它們就不會在代碼中的其他地方被意外修改。
請注意,您還應該使您的單例“實例”和地圖引用“緩存”最終。
詹姆斯是對的。 由於您正在交還一個對象,因此可以修改其內部結構,並且任何持有對該對象(地圖)的引用的內容都將反映該更改。 決賽是一個很好的答案。
這是HashMap上的java doc:
http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html
請注意,此實現不同步。 如果多個線程同時訪問哈希映射,並且至少有一個線程在結構上修改了映射,則必須在外部進行同步。 (結構修改是添加或刪除一個或多個映射的任何操作;僅更改與實例已包含的鍵關聯的值不是結構修改。)這通常通過同步自然封裝映射的某個對象來完成。 。 如果不存在此類對象,則應使用Collections.synchronizedMap方法“包裝”該映射。 這最好在創建時完成,以防止意外地不同步訪問地圖:
Map m = Collections.synchronizedMap(new HashMap(...));
或者更好,使用java.util.concurrent.ConcurrentHashMap
我發現您發布的代碼沒有任何問題,這會導致您所描述的問題。 我的猜測是,你的地理代碼緩存客戶端存在問題。
其他需要考慮的事情(其中一些非常明顯,但我認為無論如何我都會指出它們):
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()作為單例,你應該有一個私有構造函數來阻止使用新的InMemoryGeocodingCache()創建額外的緩存的可能性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.