2013-10-30 66 views
2

我遇到了使用putIfAbsent的問題,其中第二個線程將在第一個線程完成更新pk值之前嘗試訪問該值。多線程和putIfAbsent競態條件

實施例的代碼。

public <T> Object getLookupValue(final Class<T> type, String key, ConcurrentHashMap<String, T> concurrentMap) { 
     try { 

      T value = concurrentMap.get(key); 

      if (value == null) { 
       System.out.println("save"); 
       T t = type.getDeclaredConstructor(String.class).newInstance(key); 
       Object returnedValue = concurrentMap.putIfAbsent(key, t); 

       if (returnedValue == null) { 
        System.out.println("session save"); 
        session.save(t); 
        System.out.println("t ouput " + t.toString()); 
        return t; 
       } 
       return concurrentMap.get(key); 
      } else {  
       System.out.println("update" + concurrentMap.get(name)); 
       return concurrentMap.get(key); 
      } 
     } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) { 
      System.out.println("getLookupValue " + ex); 
      Logger.getLogger(LineReaderParserImpl.class.getName()).log(Level.SEVERE, null, ex); 
     } 
     return null; 
    } 

輸出

key 2008 thread 1 
save 
session save 
key 2008 thread 0 
update Year{name =2008, pk =null} 
year pk null thread 0 
save 
session save 
t ouput Year{name =2008, pk =1} 

有誰知道爲什麼線程1被線程調用之前完成0加入PK或者爲什麼線程0增加了已經產生的PK前的對象?

+0

我認爲'pk'是在調用'session.save(t)'的過程中分配的? –

+0

@MikeStrobel這是正確的,休眠分配的PK。 –

回答

2

ConcurrentHashMap API:

「不過,儘管所有操作都是線程安全的,檢索操作並不意味着鎖定,並沒有爲防止所有訪問的方式鎖定整個表的任何支持。 「

聲明您的方法參數中的ConcurrentHashMap final,並在同步塊中對其進行編輯。

public foo(final ConcurrentHashMap concurrentMap) { 
    synchronized (concurrentMap) { 
     //Your code here 
    } 
} 

這將迫使每個線程對concurrentMap Object檢索鎖,修改之前,這將解決您的競爭條件。

此外,如果您需要多個線程同時訪問地圖,但只需要在執行上述foo()方法中的應用程序代碼時鎖定,請爲獲取方法創建鎖定,而不是使用地圖本身。在第二個例子中

final Object fooLock = new Object(); 

public foo(final ConcurrentHashMap concurrentMap) { 
    synchronized (fooLock) { 
     //Your code here 
    } 
} 

位更多的解釋:

說我有一個字符串裏的ConcurrentHashMap爲它的鍵和一年它的價值。不同的線程可以訪問它來添加/刪除值,我想在某個範圍內運行年份分析,同時不會阻止我的程序在分析運行時添加/刪除值。

如果鎖定了ConcurrentHashMap,其他線程將無法添加/刪除值直到鎖被刪除。

在第二個示例中,我爲要抓取的方法創建了一個不同的鎖,因此它不會鎖定地圖本身。

ConcurrentHashMap<String, Year> concurrentMap; 

final Object lock = new Object(); 

public void runAnalysis(final ConcurrentHashMap map) { 
    /*synchronized (map) { 
     //This will cause addValue() to lock up while the analysis is running 
    }*/ 

    synchronized (lock) { 
     //Now we can run a long-running analysis and not block the addValue() method 

     //Additionally, if another thread calls runAnalysis(), it must wait to 
     //get our lock (when a current running analysis is completed) 
     //before it can start 
    } 

} 

//This method needs access to concurrentMap, so we can't lock it 
public void addValue() { 
    concurrentMap.add("key", new Year()); 
} 

說這全部在Analyzer類中聲明。我也可以聲明runAnalysis()方法,像這樣:

public synchronized void runAnalysis(ConcurrentHashMap map) { 
    //Do analysis logic 
} 

而是抓住我們的「鎖」對象的鎖,這種方法會搶在Analyzer實例的鎖。做一個稍微不同的方式,通常比創建自己的鎖更爲常見。

請注意,如果我這樣做,任何其他聲明爲「synchronized」的方法將在runAnalysis()運行時被阻止,反之亦然。你可以像使用鎖那樣複雜,但是如果你只需要一個一個方法來同步類實例,那麼獲取實例鎖看起來更乾淨,而不是單獨爲該方法創建一個鎖。

您應該查看關於Java中多線程,同步和鎖定的一些教程。

+0

謝謝,是的,我需要多個線程才能夠在同一時間訪問地圖,你能更好地解釋你的第二個例子嗎?線程外的地圖看起來像這樣,final ConcurrentHashMap yearMap = getLookupMap(Year.class); –

+0

我添加了一些細節,可能會揭示一些爲什麼第二個會更好 – hotforfeature

+0

我想我幾乎理解這一點,我猜我唯一不完全理解的部分是什麼時候調用addValue vs runAnalysis?Shouldn我只是在關鍵字不存在的情況下才添加到concurrentMap中 –