Ren*_*K N 5 java hashset concurrenthashmap java.util.concurrent
我发现我们可以使用newKeySet();或 withkeySet(default value)从ConcurrentHashMap. 当写操作超过读操作时,这是创建线程安全集的最佳方法吗?
我读到 CopyOnWriteArraySet当阅读比写作更多时,似乎更好。
欢迎所有可以帮助我们了解更多信息的答案。
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()不存在值时,情况仍然更糟。