2012-10-15 3 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을 초래한다는 것이다 프로그램 아래했다는 것을 해결한다. 내 첫 번째 분석은 null에 할당 된 값을 삭제하므로 NullPointerException이 원인입니다. 그래서 나는 아래에했다

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

그러나 위의 결과는 NullPointerException이되지 않습니다. 그래서 내 질문에 위의 경우에 NullPointerException을 던지고있는 이유는 무엇입니까? NullPointerException를 던질 것이라고, 다른 하나 돌아 null

+0

이 질문은 사실 값에 동기화

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(); } } } 

과 같을 것이다 Integer * 객체에 동기화 *를 기반으로합니다. – Aubin

+0

완료되면 왜지도에서 값을 제거해야합니까? –

+0

@NikitaBeloglazov 결국 제거해야 할 부분이 있어야합니까? 그 점이 동기화되어야하는 문제입니다. 나는 그 때 다른 해결책을 찾지 못했다. –

답변

8

음 예있을 때 그것은 단지 값이 반환하기 때문에

내가

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

을하더라도 그것은 여전히 ​​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!) 

그것은 "값이 널 (null)에 할당하기"이지 않는다 - 그것은 지정된 키에 대한 항목 Map.get 반환 null이 존재하지 않는 경우는 것을입니다.

코드가 실제로 아무 것도하지 않기 때문에 무엇을 추천해야하는지 알기가 어렵습니다. 유용한. 코드에서 달성하려는 내용을 말씀하시면 더 나은 제안을 드릴 수 있습니다.

편집 : 그게 이전 값을 반환하거나 null이없는 것처럼, 단지 putIfAbsent의 값이 작동하지 않습니다 반환, 니키타로 언급 한 바와 같이 - 당신이 항목에 대한 새로운 값을 원하는 반면.

remove 작업과 관련하여 기본적으로 getLockId 메소드를 원자화하기 위해 맵에 대한 액세스를 동기화해야한다고 생각합니다.

+1

'putIfAbsent' 결과가 여전히'NPE'가됩니다. –

+0

@AmitD : 당신이 준 코드로? 그것은 정말로 ... 필자가 제대로 멀티 코어 머신에 접근했을 때 테스트하지 말아야한다. (netbook이 첫 코드에서도 문제를 보여주지 않기 때문에) –

+0

@JonSkeet'ConncurrentMap'은'null을 리턴한다. '키가지도에 없다면. http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentMap.html#putIfAbsent%28K,%20V%29 –

2

에 대한 모든 액세스를 동기화하려고 할 수 있습니다. 따라서지도에서 가치를 얻을 때 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를 고정시킬 수 있습니다. 따라서 맵이 잠기지 않고 다른 스레드가 다른 ID에서 작동 할 수있는 동안 동일한 ID에 대한 작업을하는 동시 스레드는 2 개가 없습니다. –

0

대신 Google의 LoadingCache를 사용해보세요. 약한 참조를 사용하므로 가비지 컬렉션이 JVM에 남아 있습니다. 더 이상 필요없는 참조를 삭제할 필요가 없습니다. 또한 새로운 항목을 만드는 것과 관련된 동시성 문제를 처리합니다.값 동기화에 대한 코드는 다음과 같습니다

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

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

(아파치 2.0 라이선스로 배포 : http://www.apache.org/licenses/LICENSE-2.0)

관련 문제