当写操作多于读操作时,获得并发散列集的最佳方法是什么?

Ren*_*K N 5 java hashset concurrenthashmap java.util.concurrent

我发现我们可以使用newKeySet();或 withkeySet(default value)ConcurrentHashMap. 当写操作超过读操作时,这是创建线程安全集的最佳方法吗?

我读到 CopyOnWriteArraySet当阅读比写作更多时,似乎更好。

欢迎所有可以帮助我们了解更多信息的答案。

dav*_*xxx 5

ConcurrentHashMap.newKeySet()并且ConcurrentHashMap.keySet()返回KeySetView依赖于ConcurrentHashMap仅锁定写入且仅锁定与写入相关的键映射而不是整个映射的类。
因此,读取操作很快,但写入也很快(实际上稍微慢了一点)。

CopyOnWriteArraySet依赖的CopyOnWriteArrayList不是读锁而是写操作锁。因此,为了保证并发读取一致性,每个写入操作都会触发底层数组的副本。
因此,在不小的集合(100例如元素或更多)的情况下,CopyOnWriteArraySet写入操作(锁定整个集合+底层数组的副本)应该比KeySetView(仅锁定相关条目并唯一锁定)更昂贵。

CopyOnWriteArraySetjavadoc中 强调这一点:

  • 它最适合集合大小通常保持较小、只读操作的数量远远超过可变操作的应用程序,并且您需要在遍历过程中防止线程之间的干扰。

  • 可变操作(添加、设置、删除等)代价高昂,因为它们通常需要复制整个底层数组


这是我们比较两种行为的基准。
所述Set<String>s的用在每次迭代100个元素(“0”至99"值)初始化。
在6个第一方法是写入操作(删除,添加新的元件,覆盖现有的元素)。
而4种一个方法是读取操作(迭代,包含)。

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Threads(8)
public class SetBenchmark {

    private Set<String> keySetView;
    private Set<String> copyOnWriteArraySet;
    private Random random;

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(SetBenchmark.class.getSimpleName())
                                          .warmupIterations(5)
                                          .measurementIterations(5)
                                          .forks(1)
                                          .build();

        new Runner(opt).run();
    }

    @Setup(Level.Iteration)
    public void doSetup() {
        random = new Random(1);
        keySetView = ConcurrentHashMap.newKeySet();
        copyOnWriteArraySet = new CopyOnWriteArraySet<>();
        init(keySetView);
        init(copyOnWriteArraySet);

    }

    private void init(Set<String> set) {
        IntStream.range(0, 100)
                 .forEach(i -> {
                     final String string = String.valueOf((char) i);
                     set.add(string);
                 });

    }


    // Writing
    @Benchmark
    public void _1_keySetView_remove() {
        doRemove(keySetView);
    }

    @Benchmark
    public void _2_copyOnWriteArraySet_remove() {
        doRemove(copyOnWriteArraySet);
    }

    @Benchmark
    public void _3_keySetView_add_with_new_value() {
        doAddWithNewValue(keySetView);
    }

    @Benchmark
    public void _4_copyOnWriteArraySet_add_with_new_value() {
        doAddWithNewValue(copyOnWriteArraySet);
    }

    @Benchmark
    public void _5_keySetView_add_with_existing_value() {
        doAddWithExistingValue(keySetView);
    }

    @Benchmark
    public void _6_copyOnWriteArraySet_add_with_existing_value() {
        doAddWithExistingValue(copyOnWriteArraySet);
    }

    // Reading
    @Benchmark
    public void _7_keySetView_iterate() {
        String res = doIterate(keySetView);
    }

    @Benchmark
    public void _8_copyOnWriteArraySet_iterate() {
        String res = doIterate(copyOnWriteArraySet);
    }

    @Benchmark
    public void _9_keySetView_contains() {
        boolean res = doContains(keySetView);
    }

    @Benchmark
    public void _010_copyOnWriteArraySet_contains() {
        boolean res = doContains(copyOnWriteArraySet);
    }


    // Writing
    private void doRemove(Set<String> set) {
        set.remove(getRandomString());
    }

    private void doAddWithNewValue(Set<String> set) {
        set.add(getRandomString() + set.size());
    }

    private void doAddWithExistingValue(Set<String> set) {
        set.add(getRandomString());
    }

    // Reading
    private String doIterate(Set<String> set) {
        String result = "";
        for (String string : set) {
            result += string;
        }
        return result;
    }

    private boolean doContains(Set<String> set) {
        return set.contains(getRandomString());
    }

    // Random value with seed
    private String getRandomString() {
        return String.valueOf(random.nextInt(100));
    }

}
Run Code Online (Sandbox Code Playgroud)

有结果(分数越低越好)。

写操作:

基准模式 Cnt 分数误差单位
SetBenchmark._1_keySetView_remove 平均 61,659 ns/op
SetBenchmark._2_copyOnWriteArraySet_remove 平均 249,976 ns/op

SetBenchmark._3_keySetView_add_with_new_value 平均 240,589 ns/op
SetBenchmark._4_copyOnWriteArraySet_add_with_new_value 平均 30691,318 ns/op

SetBenchmark._5_keySetView_add_with_existing_value 平均 84,472 ns/op
SetBenchmark._6_copyOnWriteArraySet_add_with_existing_value 平均 473,592 ns/op

读取操作:

基准模式 Cnt 分数误差单位
SetBenchmark._7_keySetView_iterate 平均 13603,012 ns/op
SetBenchmark._8_copyOnWriteArraySet_iterate 平均 13626,146 ns/op

SetBenchmark._9_keySetView_contains 平均 53,081 ns/op
SetBenchmark._10_copyOnWriteArraySet_contains 平均 250,401 ns/op

Set仅对于迭代器,这两种实现的读取速度一样快。无论如何
,写入速度要慢得多,CopyOnWriteArraySet但是当我们add()不存在值时,情况仍然更糟。