Java의 다른 유형의 스레드 안전 세트


135

Java에서 스레드 안전 세트를 생성하는 다양한 구현 및 방법이있는 것 같습니다. 일부 예는 다음과 같습니다.

1) CopyOnWriteArraySet

2) Collections.synchronizedSet (세트 세트)

3) 동시 스킵리스트 세트

4) Collections.newSetFromMap (새 ConcurrentHashMap ())

5) (4)와 유사한 방식으로 생성 된 기타 세트

이 예제는 동시성 패턴 : Java 6의 동시 세트 구현에서 제공됩니다.

누군가이 예와 다른 점의 차이점, 장점 및 단점을 간단히 설명해 주시겠습니까? Java Std Docs의 모든 것을 이해하고 똑바로 유지하는 데 문제가 있습니다.

답변:


206

1) 이것은 CopyOnWriteArraySet매우 간단한 구현입니다. 기본적으로 배열에 요소 목록이 있으며 목록을 변경할 때 배열을 복사합니다. 이 시점에서 실행되는 반복 및 기타 액세스는 기존 어레이를 계속 사용하여 리더와 라이터 간의 동기화가 필요하지 않습니다 (쓰기 자체를 동기화해야 함). 일반적으로 빠른 설정 작업 (특히 contains())은 배열이 선형 시간으로 검색되므로 매우 느립니다.

자주 읽고 (반복) 자주 변경하지 않는 아주 작은 세트에만 이것을 사용하십시오. (Swings 리스너 세트는 예일 수 있지만 실제로는 세트가 아니며 EDT에서만 사용해야합니다.)

2) Collections.synchronizedSet단순히 원래 세트의 각 방법 주위에 동기화 된 블록을 감 쌉니다. 원본 세트에 직접 액세스해서는 안됩니다. 이것은 세트의 두 가지 메소드를 동시에 실행할 수 없음을 의미합니다 (하나는 다른 쪽이 끝날 때까지 차단됨)-스레드 안전하지만 여러 스레드가 실제로 세트를 사용하는 경우 동시성이 없습니다. 반복자를 사용하는 경우 반복자 호출 사이의 세트를 수정할 때 ConcurrentModificationExceptions을 피하기 위해 일반적으로 외부에서 동기화해야합니다. 성능은 원래 세트의 성능과 유사합니다 (그러나 일부 동기화 오버 헤드 및 동시에 사용되는 경우 차단).

동시성이 낮고 모든 변경 사항이 다른 스레드에 즉시 표시되도록하려면이 옵션을 사용하십시오.

3) O (log n)에서 가장 기본적인 작업을 수행 ConcurrentSkipListSet하는 동시 SortedSet구현입니다. 반복자가 추가 된 후 제거 및 읽기 / 반복을 수행 할 수 있으며, 반복자가 작성된 이후 변경 사항에 대해 반복 할 수도 있고 그렇지 않을 수도 있습니다. 대량 작업은 단순히 여러 번의 단일 호출이며 원자 적으로는 아닙니다. 다른 스레드는 그 중 일부만 관찰 할 수 있습니다.

분명히 요소에 대한 전체 순서가있는 경우에만 사용할 수 있습니다. 이것은 O (log n) 때문에 너무 크지 않은 세트에 대해 동시성이 높은 상황에 이상적인 후보처럼 보입니다.

4) ConcurrentHashMap(및 그로부터 파생 된 세트)의 경우 : 여기에서 가장 기본적인 옵션은 hashCode()O (1)에서 ( 평균적으로 좋고 빠르면 평균 이지만 HashMap /과 같이 O (n)으로 저하 될 수 있습니다) 해시 세트. 쓰기에 대한 동시성은 제한적이며 (테이블이 분할되고 필요한 액세스에서 쓰기 액세스가 동기화 됨) 읽기 액세스는 자체와 쓰기 스레드에 완전히 동시 적이지만 현재 변경 내용의 결과를 아직 보지 못할 수 있습니다. 쓴). 반복자는 생성 된 이후 변경 사항을 보거나 보지 못할 수 있으며 대량 작업은 원자 적이 지 않습니다. 크기 조정이 느리기 때문에 (HashMap / HashSet의 경우) 생성시 필요한 크기를 추정하여 3/3이 가득 찼을 때 크기를 조정할 때 크기의 1/3 이상을 사용하여이를 피하십시오.

큰 세트, 양호하고 빠른 해시 함수가 있고 맵을 작성하기 전에 세트 크기 및 필요한 동시성을 추정 할 수있는 경우이를 사용하십시오.

5) 여기서 사용할 수있는 다른 동시지도 구현이 있습니까?


1
1)의 시력 교정만으로 데이터를 새 어레이에 복사하는 프로세스를 동기화하여 잠 가야합니다. 따라서 CopyOnWriteArraySet은 동기화의 필요성을 완전히 피하지 않습니다.
CaptainHastings 2016 년

ConcurrentHashMap기초 세트, "이렇게 생성에 필요한 크기를 추정하여이를 방지하려고합니다." 세트의 크기가 75 %로 조정되므로지도에 제공하는 크기는 예상 (또는 알려진 값)보다 33 % 이상 커야합니다. 나는 사용합니다expectedSize + 4 / 3 + 1
Daren

@Daren 나는 첫 번째 +가 될 것 같아 *?
Paŭlo Ebermann

@ PaŭloEbermann 물론 ... 그래야합니다expectedSize * 4 / 3 + 1
Daren

1
대한 ConcurrentMap(나 HashMap) 다음 목록은 이진 검색 트리로 변경됩니다 같은 버킷에 도달 임계 값에 매핑 항목의 수 (나는 그것을 16 믿는) 경우 자바 8 (레드 - 블랙 트리가 precised 예정) 및 최대 경우의 모습에 시간이 될 것 O(lg n)하지 O(n).
akhil_mittal

20

각 수정에 대해 전체 세트를 사용 하고 교체 하여 동시성 관련 특성과 contains()성능 을 결합 할 수 있습니다 .HashSetCopyOnWriteArraySetAtomicReference<Set>

구현 스케치 :

public abstract class CopyOnWriteSet<E> implements Set<E> {

    private final AtomicReference<Set<E>> ref;

    protected CopyOnWriteSet( Collection<? extends E> c ) {
        ref = new AtomicReference<Set<E>>( new HashSet<E>( c ) );
    }

    @Override
    public boolean contains( Object o ) {
        return ref.get().contains( o );
    }

    @Override
    public boolean add( E e ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( current.contains( e ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.add( e );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

    @Override
    public boolean remove( Object o ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( !current.contains( o ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.remove( o );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

}

실제로 AtomicReference값을 휘발성으로 표시합니다. 이는 스레드가 오래된 데이터를 읽고 있는지 확인 happens-before하고 컴파일러가 코드를 다시 정렬 할 수 없으므로 보장을 제공 한다는 의미입니다. 그러나 get / set 메소드 만 AtomicReference사용하면 실제로 변수를 휘발성으로 멋지게 표시합니다.
akhil_mittal

이 답변은 (1) 무언가를 놓치지 않으면 모든 컬렉션 유형에 대해 작동하므로 (2) 다른 클래스는 전체 컬렉션을 한 번에 원자 적으로 업데이트 할 수있는 방법을 제공하지 않기 때문에 충분히 반박 할 수 없습니다 ... .
길리

나는이 동사를 적절하게 사용하려고 시도했지만라는 라벨이 붙은 것을 발견하여 abstract여러 가지 방법을 쓰지 않아도됩니다. 나는 그것들을 추가하기로 결정했지만와 함께 장애물에 부딪쳤다 iterator(). 모델을 손상시키지 않고이 일을 반복자를 유지하는 방법을 모르겠습니다. 나는 항상을 거쳐야 ref하고 매번 다른 기본 세트를 얻을 수있는 것 같습니다. 기본 세트에서 새 반복자를 가져와야합니다. 항목 0으로 시작하므로 나에게는 쓸모가 없습니다. 통찰력이 있습니까?
nclark

좋아, 보증은 각 고객이 정해진 시간에 고정 된 스냅 샷을 얻으므로 기본 컬렉션의 반복자가 필요한 경우 제대로 작동한다는 것입니다. 내 유스 케이스는 경쟁하는 스레드가 개별 리소스를 "클레임"하도록 허용하는 것이며 다른 버전의 세트가 있으면 작동하지 않습니다. 제 생각에 ... 나는 CopyOnWriteSet.remove (chosen_item은)는 :)에 관계없이해야 할 것이다 ... false를 반환하는 경우 내 스레드가 단지 새로운 반복자를 취득하고 다시 시도 할 필요가 추측
nclark

11

Javadocs가 도움이되지 않으면 데이터 구조에 대해 읽을 책이나 기사를 찾아야합니다. 한 눈에보기 :

  • CopyOnWriteArraySet은 컬렉션을 변경할 때마다 기본 배열의 새 복사본을 생성하므로 쓰기 속도가 느리고 반복자가 빠르고 일관됩니다.
  • Collections.synchronizedSet ()은 구식 동기화 메소드 호출을 사용하여 Set 스레드 안전을 만듭니다. 이것은 성능이 낮은 버전입니다.
  • ConcurrentSkipListSet은 일관되지 않은 배치 작업 (addAll, removeAll 등)과 반복자를 사용하여 성능 쓰기를 제공합니다.
  • Collections.newSetFromMap (new ConcurrentHashMap ())에는 ConcurrentHashMap의 의미가 있습니다 .ConcurrentHashMap의 의미는 읽기 또는 쓰기에 반드시 최적화 될 필요는 없지만 ConcurrentSkipListSet과 같이 일관되지 않은 배치 작업이 있습니다.


1

약한 참조의 동시 세트

또 다른 트위스트는 스레드 안전 약한 참조 집합입니다 .

이러한 세트는 pub-sub 시나리오 에서 가입자를 추적하는 데 편리합니다 . 가입자가 다른 장소에서 범위를 벗어나 가비지 수집 대상이 되려고 할 때 가입자는 정상적으로 구독 취소하지 않아도됩니다. 약한 참조는 가입자가 가비지 수집 후보로의 전환을 완료 할 수 있도록합니다. 가비지가 결국 수집되면 세트의 항목이 제거됩니다.

번들 된 클래스에는 이러한 세트가 직접 제공되지 않지만 몇 번의 호출로 클래스를 작성할 수 있습니다.

먼저 수업 Set을 활용하여 약한 참고 문헌 을 만드는 것으로 시작 하십시오 WeakHashMap. 이에 대한 클래스 설명서에 나와 Collections.newSetFromMap있습니다.

Set< YourClassGoesHere > weakHashSet = 
    Collections
    .newSetFromMap(
        new WeakHashMap< YourClassGoesHere , Boolean >()
    )
;

지도 의 가지도를 구성 함에 따라지도 의 값 ()Boolean여기와 관련이 없습니다 .Set

pub-sub와 같은 시나리오에서 구독자와 게시자가 별도의 스레드에서 작동하는 경우 스레드 안전성이 필요합니다 (아마도).

이 세트를 스레드로부터 안전하게하려면 동기화 된 세트로 랩핑하여 한 단계 더 나아가십시오. 에 전화를 Collections.synchronizedSet겁니다.

this.subscribers =
        Collections.synchronizedSet(
                Collections.newSetFromMap(
                        new WeakHashMap <>()  // Parameterized types `< YourClassGoesHere , Boolean >` are inferred, no need to specify.
                )
        );

이제 결과에서 구독자를 추가하고 제거 할 수 있습니다 Set. 가비지 수집이 실행 된 후 "사라지는"가입자는 결국 자동으로 제거됩니다. 이 실행 시점은 JVM의 가비지 콜렉터 구현에 따라 달라지며 현재 런타임 상황에 따라 다릅니다. 기본 WeakHashMap이 만료 된 항목을 지우는 시기와 방법에 대한 설명 및 예는 이 질문, * WeakHashMap이 계속 증가하고 있습니까, 아니면 가비지 키를 지우는가?를 참조하십시오. * .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.