什么时候CopyOnWriteArraySet有用来实现线程安全的HashSet?
在Java
,有一个名为ConcurrentHashMap的线程安全版HashMap和一个名为ConcurrentSkipListMap的线程安全版TreeMap ,但是HashSet没有ConcurrentHashSet
。
相反,通常有4种方法可以使用线程安全的Set
:
-
Set mySet = Collections.newSetFromMap(new ConcurrentHashMap());
-
Set s = Collections.synchronizedSet(new HashSet());
-
ConcurrentSkipListSet
-
CopyOnWriteArraySet
1使用ConcurrentHashMap
keySet()
来实现Set
和线程安全。
2使用synchronized
方式,似乎不推荐这种方式。
3基于ConcurrentSkipListMap
并被广泛使用。
4基于CopyOnWriteArrayList ,因此它共享CopyOnWriteArrayList的相同基本属性。 以下是从CopyOnWriteArraySet
doc中选择的: http : //docs.oracle.com/javase/8/docs/api/java/util/concurrent/CopyOnWriteArraySet.html
- 它最适合于设置大小通常很小的应用程序,只读操作数量远远超过可变操作,并且您需要在遍历期间防止线程之间的干扰。
- 它是线程安全的。
- 变异操作(添加,设置,删除等)很昂贵,因为它们通常需要复制整个底层arrays。
- 迭代器不支持mutative remove操作。
- 遍历迭代器的速度很快,不会遇到来自其他线程的干扰。
- 迭代器在构造迭代器时依赖于不变的数组快照。
由于常用1和3,为什么CopyOnWriteArraySet
存在? CopyOnWriteArraySet
什么时候有用?
补充: CopyOnWriteArraySet
基于CopyOnWriteArrayList
,而List
数据结构中的contains
操作是O(n),而Set
数据结构是针对高性能contains
操作的,有人可以解释一下吗?
当您拥有一小组用于线程安全集合的元素时,它非常有用。
一个例子是一组听众。 您需要确保唯一性并有效地迭代它们。
BTW CopyOnWriteArraySet在每个引用的基础上具有最低的开销。 它可以是其他系列大小的1/6。 如果你有很多它们,这个特别有用。
而Set数据结构是针对高性能包含操作的,有人可以解释一下吗?
COWAS在内存方面效率更高,而且对于小型集合而言,它contains
的速度比替代方案更快。 什么是“高性能”取决于用例。
写时复制结构在function上是不可变的。
Java曾经在一个非常糟糕的故事中为可写结构(如集合)提供不可变的视图。 例如,如果您有一个集合成员,并且您公开返回它,则调用者可以转身并编辑它,因此可以编辑对象的内部状态! 但是你还可以做什么,在从任何公共函数返回之前复制整个东西? 那将是毫无意义的缓慢。
这是Java历史上早期的故事。 它们几乎完全依赖于不可变对象(字符串就是一个例子)。 集合是这种模式的一个例外,因此从封装角度来看是有问题的。 当添加了CopyOnWriteArraySet
时, unmodifiableCollection
和unmodifiableSet
尚不存在(尽管unmodifiableCollection
已经在很大程度上解决了这个问题,但我仍然觉得它比其他语言提供的解决方案更麻烦,特别是在使用自定义数据结构时)。 所以这可能解释了创建CopyOnWriteArraySet
的最大动机。 您可以返回一个CopyOnWriteArraySet
而不必担心别人会修改您的对象的内部状态,也不会浪费时间制作不必要的副本。
几年前,Copy-On-Write是一种时尚,但对于multithreading编程而言,这是一个众所周知的低效思想,并且效率低于其他模型。 从您发布的文档中,他们通过创建线程本地快照加速迭代,这意味着他们花费内存来补偿。 所以只要你的数据很小就可以使用它是一个非常好的类…因为内存快照不会增加浪费很多的内存。
测试代码:
Set a = new CopyOnWriteArraySet (); for(int i=0;i<10;i++) { a.add("str" + i); } boolean flag = true; long t1 = System.currentTimeMillis(); for(int i=0;i<200000;i++) { flag = a.contains("str" + i); } System.out.println(System.currentTimeMillis() - t1); Set b = Collections.newSetFromMap(new ConcurrentHashMap()); for(int i=0;i<10;i++) { b.add("str" + i); } t1 = System.currentTimeMillis(); for(int i=0;i<200000;i++) { flag = b.contains("str" + i); } System.out.println(System.currentTimeMillis() - t1);
显示CopyOnWriteArraySet
比Collections.newSetFromMap
慢。 由于测试用例是一个非常小的Set
,只读操作, CopyOnWriteArraySet
似乎并不好。