ConcurrentSkipListMap은 언제 사용해야합니까?


82

Java에서는 ConcurrentHashMap더 나은 multithreading솔루션이 있습니다. 그럼 언제 사용해야하나요 ConcurrentSkipListMap? 중복입니까?

이 두 가지 사이의 다중 스레딩 측면이 일반적입니까?

답변:


80

이 두 클래스는 몇 가지면에서 다릅니다.

ConcurrentHashMap 은 계약의 일부로 작업의 런타임을 보장하지 않습니다 *. 또한 특정로드 요소 (대략 동시에 수정하는 스레드 수)에 대한 튜닝을 허용합니다.

반면 ConcurrentSkipListMap 은 다양한 작업에서 평균 O (log (n)) 성능을 보장합니다. 또한 동시성을위한 조정을 지원하지 않습니다. ConcurrentSkipListMap또한 ConcurrentHashMapCeilingEntry / Key, floorEntry / Key 등 을 사용 하지 않는 여러 작업 이 있습니다. 또한 정렬 순서를 유지합니다. 그렇지 않으면 ConcurrentHashMap.

기본적으로 서로 다른 사용 사례에 대해 서로 다른 구현이 제공됩니다. 빠른 단일 키 / 값 쌍 추가 및 빠른 단일 키 조회가 필요한 경우 HashMap. 더 빠른 순회 순회가 필요하고 삽입에 대한 추가 비용을 감당할 수있는 경우 SkipListMap.

* 구현이 O (1) 삽입 / 조회에 대한 일반적인 해시 맵 보장과 대략 일치한다고 생각하지만; 재해 싱 무시


확인. Log (n)은 괜찮지 만 ConcurrentSkipListMap은 공간 효율적입니까?
DKSRathore

1
건너 뛰기 목록은 반드시 Hashtables보다 크지 만 ConcurrentHashMap의 조정 가능한 특성으로 인해 동등한 ConcurrentSkipListMap보다 큰 Hashtable을 구성 할 수 있습니다. 일반적으로 건너 뛰기 목록은 더 크지 만 크기는 동일 할 것으로 예상합니다.
Kevin Montrose

"동시성을위한 튜닝도 지원하지 않습니다.". 왜? 링크는 무엇입니까?
Pacerier 2012

2
@Pacerier-동시성이기 때문에 튜닝을 지원한다는 의미가 아니라 동시 성능에 영향을 미치는 매개 변수를 튜닝 할 수 없다는 의미입니다 (ConcurrentHashMap이 수행하는 동안).
Kevin Montrose

@KevinMontrose Ic는 "동시성 조정도 지원하지 않습니다."라는 뜻입니다.
Pacerier 2012


8

성능면에서 skipList지도로 사용하는 경우-10 ~ 20 배 느려진 것으로 보입니다. 다음은 내 테스트 결과입니다 (Java 1.8.0_102-b14, win x32)

Benchmark                    Mode  Cnt  Score    Error  Units
MyBenchmark.hasMap_get       avgt    5  0.015 ?  0.001   s/op
MyBenchmark.hashMap_put      avgt    5  0.029 ?  0.004   s/op
MyBenchmark.skipListMap_get  avgt    5  0.312 ?  0.014   s/op
MyBenchmark.skipList_put     avgt    5  0.351 ?  0.007   s/op

그리고 그 외에도 일대일 비교가 실제로 의미가있는 사용 사례입니다. 이 두 컬렉션을 모두 사용하여 최근에 사용한 항목 캐시 구현. 이제 skipList의 효율성은 이벤트가 더 모호해 보입니다.

MyBenchmark.hashMap_put1000_lru      avgt    5  0.032 ?  0.001   s/op
MyBenchmark.skipListMap_put1000_lru  avgt    5  3.332 ?  0.124   s/op

다음은 JMH에 대한 코드입니다 (으로 실행 됨 java -jar target/benchmarks.jar -bm avgt -f 1 -wi 5 -i 5 -t 1).

static final int nCycles = 50000;
static final int nRep = 10;
static final int dataSize = nCycles / 4;
static final List<String> data = new ArrayList<>(nCycles);
static final Map<String,String> hmap4get = new ConcurrentHashMap<>(3000, 0.5f, 10);
static final Map<String,String> smap4get = new ConcurrentSkipListMap<>();

static {
    // prepare data
    List<String> values = new ArrayList<>(dataSize);
    for( int i = 0; i < dataSize; i++ ) {
        values.add(UUID.randomUUID().toString());
    }
    // rehash data for all cycles
    for( int i = 0; i < nCycles; i++ ) {
        data.add(values.get((int)(Math.random() * dataSize)));
    }
    // rehash data for all cycles
    for( int i = 0; i < dataSize; i++ ) {
        String value = data.get((int)(Math.random() * dataSize));
        hmap4get.put(value, value);
        smap4get.put(value, value);
    }
}

@Benchmark
public void skipList_put() {
    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentSkipListMap<>();

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            map.put(key, key);
        }
    }
}

@Benchmark
public void skipListMap_get() {
    for( int n = 0; n < nRep; n++ ) {
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            smap4get.get(key);
        }
    }
}

@Benchmark
public void hashMap_put() {
    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentHashMap<>(3000, 0.5f, 10);

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            map.put(key, key);
        }
    }
}

@Benchmark
public void hasMap_get() {
    for( int n = 0; n < nRep; n++ ) {
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            hmap4get.get(key);
        }
    }
}

@Benchmark
public void skipListMap_put1000_lru() {
    int sizeLimit = 1000;

    for( int n = 0; n < nRep; n++ ) {
        ConcurrentSkipListMap<String,String> map = new ConcurrentSkipListMap<>();

        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            String oldValue = map.put(key, key);

            if( (oldValue == null) && map.size() > sizeLimit ) {
                // not real lru, but i care only about performance here
                map.remove(map.firstKey());
            }
        }
    }
}

@Benchmark
public void hashMap_put1000_lru() {
    int sizeLimit = 1000;
    Queue<String> lru = new ArrayBlockingQueue<>(sizeLimit + 50);

    for( int n = 0; n < nRep; n++ ) {
        Map<String,String> map = new ConcurrentHashMap<>(3000, 0.5f, 10);

        lru.clear();
        for( int i = 0; i < nCycles; i++ ) {
            String key = data.get(i);
            String oldValue = map.put(key, key);

            if( (oldValue == null) && lru.size() > sizeLimit ) {
                map.remove(lru.poll());
                lru.add(key);
            }
        }
    }
}

2
ConcurrentSkipListMap은 동시 적이 지 않은 카운터 부분 인 TreeMap과 비교되어야한다고 생각합니다.
abbas

2
abbas가 언급했듯이 성능을 ConcurrentHashMap과 비교하는 것은 어리석은 것 같습니다. ConcurrentSkipListMap의 목적은 (a) 동시성을 제공하고 (b) 키 간의 정렬 순서를 유지하는 것입니다. ConcurrentHashMap은 a를 수행하지만 b는 수행하지 않습니다. Tesla와 덤프 트럭의 0 ~ 60 속도를 비교하는 것은 목적이 다르기 때문에 무의미합니다.
Basil Bourque

성능 메트릭스를 모르지만 어느 것이 Tesla이고 어느 것이 "덤프 트럭"인지 모릅니다. :) 또한 ... 메트릭없이 "b"의 가격을 알 수 없습니다. 따라서 성능 비교는 일반적으로 유용합니다.
Xtra Coder

1
트리 맵에 비교를 추가 할 수 있습니다! : D
simgineer

4

그렇다면 언제 ConcurrentSkipListMap을 사용해야합니까?

(a) 키를 정렬 된 상태로 유지해야하거나 (b) 탐색 가능한 맵의 첫 번째 / 마지막, 헤드 / 테일 및 서브맵 기능이 필요한 경우.

ConcurrentHashMap클래스는 구현 ConcurrentMap으로 수행, 인터페이스를 ConcurrentSkipListMap. 그러나 당신은 또한의 행동하려는 경우 SortedMapNavigableMap사용을ConcurrentSkipListMap

ConcurrentHashMap

  • ❌ 정렬 됨
  • ❌ 탐색 가능
  • ✅ 동시

ConcurrentSkipListMap

  • ✅ 정렬 됨
  • ✅ 탐색 가능
  • ✅ 동시

다음은 MapJava 11과 함께 번들로 제공되는 다양한 구현 의 주요 기능을 안내하는 표입니다 . 클릭 / 탭하여 확대 / 축소합니다.

기능을 비교하는 Java 11의 맵 구현 표

Google GuavaMap 와 같은 다른 소스에서 다른 구현 및 유사한 데이터 구조를 얻을 수 있습니다 .


이 사진은 굉장합니다. 일반 컬렉션과 동시 컬렉션의 일부 또는 전부에 대해 비슷한 사진이 있습니까?
AnV

1
@Anv 감사합니다. 차트를 만드는 데 상당한 작업이 필요했습니다. Java 사용자 그룹을위한 프레젠테이션의 일부입니다. A map to Java Maps . 그리고 아니요, 관련 클래스 및 인터페이스에 대해String 다른 클래스 다이어그램을 하나만 만들었습니다 .
Basil Bourque

1

워크로드를 기반으로 ConcurrentSkipListMap은 범위 쿼리가 필요한 경우 KAFKA-8802 에서와 같이 동기화 된 메서드를 사용하는 TreeMap보다 느릴 수 있습니다 .


공유해 주셔서 감사합니다. 내 프로젝트 중 하나에서 TreeMap을 ConcurrentSkipListMap으로 대체하려고 생각하고 있으므로 이것을 아는 것이 좋습니다! ConcurrentSkipListMap이 느린 이유에 대한 자세한 내용과 성능 비교에 대한 자세한 내용이 있습니까?
yusong

0

ConcurrentHashMap : 다중 스레드 인덱스 기반 get / put을 원할 때 인덱스 기반 작업 만 지원됩니다. Get / Put은 O (1)입니다.

ConcurrentSkipListMap : 키별로 상위 / 하위 n 개 항목 정렬, 마지막 항목 가져 오기, 키별로 정렬 된 전체 맵 가져 오기 / 횡단 등과 같이 가져 오기 / 넣기보다 더 많은 작업을 수행합니다. 복잡성은 O (log (n))이므로 넣기 성능은 그렇지 않습니다. ConcurrentHashMap만큼 훌륭합니다. SkipList를 사용한 ConcurrentNavigableMap의 구현이 아닙니다.

요약하면 단순한 get 및 put이 아닌 정렬 된 기능이 필요한 맵에서 더 많은 작업을 수행하려는 경우 ConcurrentSkipListMap을 사용합니다.

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