2012-01-19 4 views
5

내가 예를 들어, 고유 키가있는 캐시의 어느 특정 구현을 작성해야하지만 중복 값을 포함 할 수 있습니다멀티 스레드 사용이

"/path/to/one" -> 1 
"/path/to/two" -> 2 
"/path/to/vienas" -> 1 
"/path/to/du" -> 2 
클래스는 비 차단 읽기 제공 할 필요가

/키 조회뿐만 아니라 일반적인 작성/업데이트/삭제 뮤 테이터도 제공합니다. 한 동시 쓰기가 서로의 상단에 실행하지 않는 한 - 예를 들어, 제거 값 2

"/path/to/one" -> 1 
"/path/to/vienas" -> 1 

지금까지 그렇게 성능이 문제가되지 않습니다 쓰기로 쓰기보다 중요한 것이 캐시 읽어 결과를해야한다. 전체 항목 수는 1000 개 미만이 될 수 있으므로 때때로 값을 반복하는 것이 여전히 경제적입니다. 나는 ConcurrentHashMap 또한 비 차단 내 모든 노력이 무의미 할 것이다 읽는 제공하는 것을 깨달았다 위를 기록 후

// 
// tl;dr all writes are synchronized on a single lock and each 
// resets the reference to the volatile immutable map after finishing 
// 
class CopyOnWriteCache { 
    private volatile Map<K, V> readOnlyMap = ImmutableMap.of(); 

    private final Object writeLock = new Object(); 

    public void add(CacheEntry entry) { 
     synchronized (writeLock) { 
     readOnlyMap = new ImmutableMap.Builder<K, V>() 
      .addAll(readOnlyMap) 
      .add(entry.key, entry.value) 
      .build(); 
     } 
    } 

    public void remove(CacheEntry entry) { 
     synchronized (writeLock) { 
     Map<K, V> filtered = Maps.filterValues(readOnlyMap, somePredicate(entry)); 
     readOnlyMap = ImmutableMap.copyOf(filtered); 
     } 
    } 

    public void update(CacheEntry entry) { 
     synchronized (writeLock) { 
     Map<K, V> filtered = Maps.filterValues(readOnlyMap, somePredicate(entry)); 
     readOnlyMap = new ImmutableMap.Builder<K, V>() 
      .addAll(filtered) 
      .add(entry.key, entry.value) 
      .build(); 
     } 
    } 

    public SomeValue lookup(K key) { 
     return readOnlyMap.get(key); 
    } 
} 

하지만 문이있다 :

그래서 나는이 (의사 코드) 같은 것을 썼다 눈썹 제기 자바 독 : 나는 final ConcurrentHashMapvolatile ImmutableMap의 사용을 대체하고 모든 synchronized 블록을 제거한다면

iterators are designed to be used by only one thread at a time 

는 동시 뮤 테이터를합니다 경쟁한다는 것이 가능하다 서로를 무효로합니까? 예를 들어, remove에 대한 두 개의 동시 호출이 경쟁 조건으로 이어지는 방식으로 첫 번째 remove의 결과가 완전히 무효화되는 것을 상상할 수 있습니다. 내가 볼 수

유일한 개선은있는 그대로 synchronized를 떠나 final ConcurrentHashMap를 사용하여 나는 적어도 데이터의 불필요한 복사를 피할 수 있다는 것입니다.

의미가 있습니까? 아니면 여기에서 뭔가를 간과 할 수 있습니까? 누구든지이 솔루션의 다른 대안을 제안 할 수 있습니까?

답변

5

이 교체 작업을 수행 한 경우 한 번에 주어진 반복자를 사용하는 스레드는 하나뿐입니다. 경고는 두 스레드가 동일한 Iterator 인스턴스를 사용해서는 안된다는 것을 의미합니다. 두 스레드가 동시에 반복 할 수는 없습니다.

ConcurrentMap의 단일 원자 조작에서 제거 조작을 수행 할 수 없으므로 동시 스레드가 맵을 중간 상태로 볼 수 있습니다. 하나의 값은 제거되었지만 제거되지 않았습니다. 다른 하나.

필자는 쓰기 성능에 문제가 없다고 생각하기 때문에 더 빠를 것이라고 확신하지는 않지만 각 쓰기에서 맵 복사를 피하기 위해 할 수있는 일은 ReadableLockLock을 사용하여 변경 가능한 ConcurrentMap. 모든 읽기는 여전히 동시지만지도에 쓰면 다른 모든 스레드가지도에 액세스하지 못하게됩니다. 그리고 수정 될 때마다 새로운 사본을 만들 필요가 없습니다.

2

ConcurrentHashMap을 여러 개의 반복자/다중 스레드를 통해 동시에 변경하는 것이 좋습니다. 반복자의 단일 인스턴스를 여러 스레드에 전달하고 동시에 사용할 필요가 없다는 것입니다.

따라서 ConcurrentHashMap을 사용하면 synchronized을 그 자리에 남겨 둘 필요가 없습니다. JB Nizet이 지적했듯이, 현재와 현재 구현의 차이점은 중간 상태의 가시성입니다.만약 당신이 그것에 대해 신경 쓰지 않는다면, 구현이 가장 간단하고 읽기 또는 쓰기 성능에 대해 걱정할 필요가 없기 때문에 ConcurrentHashMap을 사용하는 것이 제가 선호하는 선택 일 것입니다.