2008-10-28 121 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中