2012-03-02 57 views
4

我有一個關於HashMap同步的問題。背景是我正在嘗試實施一種簡單的暴力檢測方法。我將使用以用戶名爲關鍵字的地圖,用於保存用戶失敗的登錄嘗試次數。如果登錄失敗,我想做的事情是這樣的:HashMap同步映射值增值

Integer failedAmount = myMap.get("username"); 
    if (failedAmount == null) { 
     myMap.put("username", 1); 
    } else { 
     failedAmount++; 
     if (failedAmount >= THRESHOLD) { 
      // possible brute force detected! alert admin/slow down login 
      ///or whatever 
     } 
     myMap.put("username", failedAmount); 
    } 

我此刻心目中的機制是相當簡單:我只是在午夜跟蹤此全日和清除()HashMap中或類似的東西。

所以我的問題是: 什麼是最好的/最快的Map實現,我可以使用嗎?我需要一個完全同步的Map(Collections.sychronizedMap())還是一個ConcurrentHashMap是否足夠?或者甚至可能只是一個正常的HashMap?如果有幾個增量滑過,我想這不是什麼大問題?

回答

5

我會使用的ConcurrentHashMapAtomicIntegerhttp://docs.oracle.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicInteger.html的組合。

使用AtomicInteger不會幫你與比較,但它會幫助你保持數字準確 - 沒必要做++和認沽兩個步驟。

ConcurrentHashMap,我會使用putIfAbsent方法,這將消除您的第一個if條件。

AtomicInteger failedAmount = new AtomicInteger(0); 

failedAmount = myMap.putIfAbsent("username", failedAmount); 

if (failedAmount.incrementAndGet() >= THRESHOLD) { 
    // possible brute force detected! alert admin/slow down login 
    ///or whatever 
} 
+0

你仍然需要一個同步的塊。 – aioobe 2012-03-02 16:42:26

+0

你能解釋一下爲什麼嗎? – nwinkler 2012-03-02 16:43:43

+0

Wooops。沒有看到putIfAbsent :)真正優雅的解決方案:) +1! – aioobe 2012-03-02 19:42:18

1

我在這裏看到的最簡單的解決方案是將這些代碼提取到一個單獨的函數中並使其同步。 (或將所有代碼放入同步塊)。所有其他保持不變。地圖變量應該是最終的。

3

除非你同步的整個代碼塊,做了更新,它不會如您所願反正工作。

一個同步的地圖只是確保,如果你同時調用,也就是說,put幾次沒什麼討厭的發生。它不確定

myMap.put("username", myMap.get("username") + 1); 

原子執行。

您應該確實同步執行更新的整個塊。通過使用一些Semaphore或使用​​關鍵字。例如:

final Object lock = new Object(); 

... 

synchronized(lock) { 

    if (!myMap.containsKey(username)) 
     myMap.put(username, 0); 

    myMap.put(username, myMap.get(username) + 1); 

    if (myMap.get(username) >= THRESHOLD) { 
     // possible brute force detected! alert admin/slow down login 
    } 
} 
1

使用同步HashMapConcurrentHashMap只需要,如果你的監控應用程序是多線程的。如果是這種情況,ConcurrentHashMap在高負載/爭用的情況下具有明顯更好的性能。

如果連一個 writer/updater線程存在,我不敢用多線程的非同步結構。這不僅僅是失去一些增量的問題 - HashMap本身的內部結構可能會被破壞。

這就是說,如果你想確保沒有增量丟失,那麼即使同步Map是不夠的:

  • 用戶X試圖登錄
  • 線程A獲得數N代表「用戶X」
  • 用戶X試圖登錄再次
  • 線程B得到計數N表示 「用戶X」
  • 甲把N + 1到地圖
  • 乙把N + 1到地圖
  • 地圖現在包含N + 1而不是N + 2

爲了避免這種情況,既可以使用一個同步塊爲整個獲取/設置操作或使用你的計數器沿着AtomicInterer而不是Integer

2

我看到的最好方法是將失敗計數器與用戶對象一起存儲,而不是存儲在某種全局映射中。這樣,同步問題甚至不會出現。

如果你仍然想用地圖去,你可以用一個部分同步的方式獲得贈品,如果您使用的是可變計數器對象:

static class FailCount { 
    public int count; 
} 

// increment counter for user 
FailCount count; 
synchronized (lock) { 
    count = theMap.get(user); 
    if (count == null) { 
     count = new FailCount(); 
     theMap.put(user, count); 
    } 
} 
count.count++; 

但最有可能在這裏任何優化的嘗試是浪費時間。它不像你的系統會每秒處理數百萬次登錄失敗,所以你的原始代碼應該沒問題。

+0

+1您可以使FailCount成爲AtomicInteger以使其線程安全。 – 2012-03-02 15:00:28