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

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

1) CopyOnWriteArraySet

2) Collections.synchronizedSet (세트 세트)

3) 동시 스킵리스트 세트

4) Collections.newSetFromMap (새 ConcurrentHashMap ())

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

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

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



답변

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) 여기서 사용할 수있는 다른 동시지도 구현이 있습니까?


답변

각 수정에 대해 전체 세트를 사용 하고 교체 하여 동시성 관련 특성과 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;
            }
        }
    }

}


답변

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

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

답변

약한 참조의 동시 세트

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

이러한 세트는 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이 계속 증가하고 있습니까, 아니면 가비지 키를 지우는가?를 참조하십시오. * .


답변