簡體   English   中英

線程問題在Java HashMap中

[英]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

我發現您發布的代碼沒有任何問題,這會導致您所描述的問題。 我的猜測是,你的地理代碼緩存客戶端存在問題。

其他需要考慮的事情(其中一些非常明顯,但我認為無論如何我都會指出它們):

  1. 你有哪兩個郵政編碼問題? 您確定它們在源系統中沒有相同的地理編碼嗎?
  2. 你確定你不小心比較兩個相同的郵政編碼嗎?

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM