2011-01-25 5 views
6

값을 다른 원자와 교환 할 수있는 참조 유형을 구현할 방법이 있습니까? 자바에서 원자 적으로 교환 할 수있는 AtomicReference를 생성 할 수 있습니까?


우리는하지만 다른 AtomicReference와 지역 변수로 교환 할 수 AtomicReference가 있습니다.

AtomicReference r1 = new AtomicReference("hello"); 
AtomicReference r2 = new AtomicReference("world"); 

을 두 작업의 조합을 교환 :

당신은 할 수

r1.set(r2.getAndSet(r1.get())); 

을하지만이 모두가 "hello"를 포함 할 경우, 사이에 일관성이없는 상태에서 그들을 떠난다. 또한 당신이 원자 적으로 그것들을 교환 할 수있다하더라도, 당신은 여전히 ​​그것들을 원자 적으로 (쌍으로) 읽을 수 없습니다. 내가 할 수 있도록하고 싶습니다 무엇


입니다 : 다음

PairableAtomicReference r1 = new PairableAtomicReference("hello"); 
PairableAtomicReference r2 = new PairableAtomicReference("world"); 
AtomicRefPair rp = new AtomicRefPair(r1, r2); 

Object[] oldVal, newVal; 
do { 
    oldVal = rp.get(); 
    newVal = new Object[] {oldVal[1], oldVal[0]}; 
} while (! rp.compareAndSet(oldVal, newVal)); 

값을 교환하고, 다른 스레드에서하기 :

AtomicRefPair otherRP = new AtomicRefPair(r1, r2); 
System.out.println(Arrays.toString(otherRP.get())); 

출력은 어느 것이 든 [hello, world] 또는 [world, hello].

주 : r1 다른 r3 말,

  • r1r2이 작업을 위해 쌍으로하지만, 다른 스레드는 독립적으로 짝을 것입니다 가능성이
  • (불행하게도 그것이 내가 this solution을 사용할 수 없음을 의미합니다.) 수십만 개의 이러한 참조가 될 것이므로 세계 ReentrantLock은 큰 병목이 될 것입니다.
  • rpotherRP은 반드시 스레드간에 공유되지 않으므로 단순히 잠그면 작동하지 않습니다. 그들은 interned 일 수 있지만 인턴 풀은 다른 병목 현상이 될 자체 동기화가 필요합니다.
  • 여기에 2 개의 참조 그룹 만 만들었지 만 3 개 이상 그룹으로 묶을 수있는 능력은 보너스가 될 것입니다.

AtomicRefPair의 잠금 해제 버전을 구현할 수 있습니까? 나는 그렇지 않다는 직감을 가지고있다. 그러나 그렇지 않다면 어쩌면 어딘가에 이유를 설명하는 기사가있을 것이다.관련


: How do I atomically swap 2 ints in C#?

+1

Guava에는 ConcurrentHashMap을 사용하는 Interner가 있으므로, 평균적으로 경쟁이 임의적으로 작을 수 있습니다. – maaartinus

답변

3

거기에 좋은 솔루션이지만, 다음과 같은 추한 사람이 일을 할 수 있다면 나도 몰라 : 대신 AtomicReference의

public final class MyReference<T> extends ReentrantLock implements Comparable<MyReference<T>> { 
    public MyReference() { 
     id = counter.incrementAndGet(); 
    } 

    public void swap(MyReference<T> other) { 
     if (id < other.id) { 
      lock(); 
      other.lock(); 
     } else { 
      other.lock(); 
      lock(); 
     } 
     final T tmp = value; 
     value = other.value; 
     other.value = tmp; 
     unlock(); 
     other.unlock(); 
    } 

    public static <T> List<T> consistentGet(List<MyReference<T>> references) { 
     final ArrayList<MyReference<T>> sortedReferences = Lists.newArrayList(references); 
     Collections.sort(sortedReferences); 
     for (val r : sortedReferences) r.lock(); 
     final List<T> result = Lists.newArrayListWithExpectedSize(sortedReferences.size()); 
     for (val r : references) result.add(r.value); 
     for (val r : sortedReferences) r.unlock(); 
     return result; 
    } 

    @Override 
    public int compareTo(MyReference<T> o) { 
     return id < o.id ? -1 : id > o.id ? 1 : 0; 
    } 

    private final static AtomicInteger counter = new AtomicInteger(); 

    private T value; 
    private final int id; 
} 
  • 사용 MyReference.
  • 많은 잠금을 사용하지만 어느 것도 글로벌입니다.
  • 고정 된 순서로 잠금을 획득하므로 교착 상태가 발생하지 않습니다.
  • 롬복과 구아바를 사용하여 컴파일합니다 (모조 코드없이 의사 코드로 사용).
+0

+1. 거의 내가 제안하고 싶었던 것. 그러나 op에는 "수십만 개의 이러한 참조"가 필요합니다. 아마'ConcurrentHashMap'이 어떻게 작동하는지와 거의 같은 방식으로 파티션을 그룹화하고 잠금을이 파티션과 연관시키는 것이 더 나을 것입니다. 파티션 수가 상당히 많으면 논쟁의 가능성이 낮아야합니다. – axtavt

+0

ReentrantLock은 공백 객체에 대한 참조가 하나만 있기 때문에 요정처럼 작습니다. 따라서 1 백만 개에 불과한 비용은 24 MB입니다 (참조 당 8B, 객체 당 16B 가정). – maaartinus

+0

@maaartinus, 롬복에 대한 참고 문헌은 어디입니까? – finnw

4

쌍을 보유하는 변경 불가능한 클래스가 있습니다. 그것이 당신의 원자입니다. 쌍을 바꾸는 것은 원자를 대체하는 것을 의미합니다.

업데이트 : 질문이 명확하지 않습니다. 그러나 일반적으로 여러 변수로 구성된 동시 시스템의 경우 하나는 시스템 상태의 스냅 샷을 취해야 할 수도 있습니다.

  • 일단 스냅 샷을 가져 오면 변경되지 않습니다.
  • 여러 변수를 동시에 변경하여 시스템 상태를 원자 적으로 업데이트합니다. 내 업데이트와 이전 스냅 샷 (내 계산 기반) 사이에 다른 업데이트가 필요하지 않을 수도 있습니다.
  • 너무 많은 리소스를 소비하지 않으면 시스템을 스냅 샷으로 직접 모델링 할 수 있습니다.

    +0

    질문의 첫 번째 글 머리 기호를 참조하십시오. – finnw

    +0

    @finnw -이 답변에 어떤 문제가 있습니까? 이것은 합리적인 (그리고 훨씬 덜 복잡한) 솔루션입니다. – jtahlborn

    +0

    나는 'r1'과'r2'가 하나의 연산을 위해 쌍을 이루고'r1'과'r3'이 다른 연산을 위해 짝을 이룰 수 있다는 사실을 언급했습니다. 이것은 원칙적으로 작동 할 수 있지만 참조를 동적으로 다시 그룹화하는 방법이 필요합니다. – finnw

    관련 문제