2012-10-15 75 views
1

此問題是基於Synchronizing on an Integer value在Integer上同步會導致NullPointerException

那裏的解決方案似乎很好,只有小問題,它沒有解決如何刪除ConcurrentHashMap中的值的問題。

因此,要解決,我做到了以下程序

import java.util.concurrent.ConcurrentHashMap; 

public class Example { 

    private final ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>(); 

    public void doSomething(int i) { 
     synchronized (getLockForId(i)) { 
      concurrentHashMap.remove(i); 
     } 
    } 

    public Integer getLockForId(int id) { 
     concurrentHashMap.putIfAbsent(id, id); // I want to replace these two 
               // operation with single one 
               // since it seems the cause of 
               // NPE 
     return concurrentHashMap.get(id); 
    } 

    public static void main(String[] args) { 
     final Example example = new Example(); 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       int i = 0; 
       while (true) { 
        example.doSomething(++i); 
       } 
      } 
     }).start(); 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       int i = 0; 
       while (true) { 
        example.doSomething(++i); 
       } 
      } 
     }).start(); 
    } 
} 

問題是,它總是導致NullPointerException。我的第一個分析是因爲我刪除了它被賦值爲空的值,所以它導致了NullPointerException。所以,我沒有下文

Object obj = new Object(); 
    synchronized (obj) { 
     obj = null; 
    } 

但上面並沒有導致NullPointerException。所以我的問題是爲什麼它在上面的情況下拋出NullPointerException

即使我做

public Integer getLockForId(int id) { 
    return concurrentHashMap.putIfAbsent(id, id); 
} 

仍然導致NullPointerException,因爲它只返回值時,有其他人返回null

+0

這個問題基於整數*對象*不是值的同步。 – Aubin

+0

爲什麼在完成後需要從地圖中刪除值? –

+0

@NikitaBeloglazov最終必須有一點,那些需要被刪除的權利?它需要同步的問題。那麼我看不到其他解決方案。 –

回答

8

嗯,是的,那拋出NullPointerException。考慮這一模式:

Thread 1      Thread 2 

putIfAbsent (puts) 
get (returns non-null) 
acquire monitor 
           putIfAbsent (doesn't put) 
remove (removes value) 
           get (returns null) 
           acquire monitor (bang!) 

這並不是說「值會被分配給空」 - 那就是Map.get返回空值,如果有給定鍵的條目。

很難知道推薦什麼,因爲你的代碼真的不會做任何事情有用的。如果你可以說你在real代碼中試圖達到的目標,我們可以給你提供更好的建議。

編輯:正如尼基塔指出,剛剛回國的putIfAbsent值不起作用,因爲它返回以前值,或者null如果它是不存在的 - 而你想條目的值。

我懷疑你必須同步對地圖的訪問,基本上,使getLockId方法相對於remove操作是原子的。

+1

'putIfAbsent'的返回結果仍然會導致'NPE' –

+0

@AmitD:使用您提供的代碼?它真的不應該......當我有機會訪問正確的多核心機器時會測試它(因爲即使使用第一個代碼,我的上網本也沒有顯示出問題)。 –

+0

@JonSkeet'ConncurrentMap'返回null '如果鑰匙不在地圖中。 http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentMap.html#putIfAbsent%28K,%20V%29 –

2

您可以嘗試使所有對concurrentHashMap的訪問同步。因此,當您從地圖獲得價值時,您需要在concurrentHashMap上進行同步以及何時將其刪除。類似這樣的:

public void doSomething(int i) { 
    synchronized (getLockForId(i)) { 
     // do stuff 
     synchronized (concurrentHashMap) { 
      concurrentHashMap.remove(i); 
     } 
    } 
} 


public Integer getLockForId(int id) { 
    synchronized (concurrentHashMap) { 
     concurrentHashMap.putIfAbsent(id, id); 
     return concurrentHashMap.get(id); 
    } 
} 
+0

然後仍然沒有這個[有問題的解決方案]解決方案的意義:)因爲你不再有多個鎖,你只能在一個鎖上同步。 –

+0

我認爲你需要同時鎖定。因爲當你從地圖獲取/發佈id時進行操作 - 你在這張地圖上使用鎖定。當你使用當前id的東西 - 你有這個ID的鎖。所以沒有2個併發線程在同一個id上做東西,而map沒有被鎖定,其他線程可以在其他id上工作。 –

0

請考慮使用google的LoadingCache代替。它使用弱引用,因此垃圾收集留給JVM;您不必刪除不再需要的引用。它處理與創建新條目有關的併發問題。對於一個數值同步的代碼應該是這樣的:

import com.google.common.cache.CacheBuilder; 
import com.google.common.cache.CacheLoader; 
import com.google.common.cache.LoadingCache; 

import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 

public class ValueSynchronizer<T> { 
    private final LoadingCache<T, Lock> valueLocks; 

    public ValueSynchronizer() { 
    valueLocks = CacheBuilder.newBuilder().build(
     new CacheLoader<T, Lock>() { 
     public Lock load(final T id) { 
      return new ReentrantLock(); 
     } 
     }); 
    } 

    public void sync(final T onValue, final Runnable toDo) 
    { 
    final Lock lock = valueLocks.getUnchecked(onValue); 
    lock.lock(); 

    try { 
     toDo.run(); 
    } 
    finally { 
     lock.unlock(); 
    } 
    } 
} 

在值實際上同步應該是這樣的:

private final ValueSynchronizer<Long> retrySynchronizer 
    = new ValueSynchronizer<>(); 

@Override 
public void onApplicationEvent(final EventThatCanBeSentMultipleTimes event) 
{ 
    retrySynchronizer.sync(event.getEventId(),() -> 
    { 
    //...Your code here. 
    }); 
} 

(Apache 2.0許可協議授權:http://www.apache.org/licenses/LICENSE-2.0

相關問題