Java中不同types的线程安全集
Java中似乎有很多不同的实现和方法来生成线程安全的集合。 一些例子包括
1) CopyOnWriteArraySet
2) Collections.synchronizedSet(Set集)
3) ConcurrentSkipListSet
4) Collections.newSetFromMap(new ConcurrentHashMap())
5)以类似于(4)的方式生成的其他集合
这些示例来自并发模式:Java 6中的并发集实现
有人可以简单地解释一下这些例子和其他例子的差异,优点和缺点吗? 我无法理解并保持Java Std Docs中的所有内容。
1) CopyOnWriteArraySet
是一个相当简单的实现 – 它基本上有一个数组中的元素列表,当更改列表时,它复制数组。 此时正在运行的迭代和其他访问继续使用旧数组,避免了读写器之间的同步(尽pipe写本身需要同步)。 正常的快速设置操作(特别是contains()
)在这里非常慢,因为数组将在线性时间内search。
只能用于非常小的集合,这些集合会经常被读取(迭代)并且很less被改变。 (Swings listener-sets就是一个例子,但是这些并不是真正的集合,只能从EDT中使用)。
2) Collections.synchronizedSet
将简单地围绕原始集合的每个方法包装同步块。 您不应直接访问原始设置。 这意味着没有两个方法可以同时执行(一个会阻塞,直到另一个完成) – 这是线程安全的,但如果多个线程真的使用该集合,则不会有并发性。 如果使用迭代器,通常仍然需要在外部进行同步,以避免在迭代器调用之间修改集合时出现ConcurrentModificationExceptionexception。 性能将像原始设置的性能(但有一些同步开销,如果同时使用阻塞)。
如果您只有低并发性,并且要确保所有更改都可以立即显示给其他线程,请使用此选项。
3) ConcurrentSkipListSet
是并发SortedSet
实现,其中O(log n)中的大部分基本操作。 它允许并发添加/删除和读取/迭代,其中迭代可能会或可能不会告诉自迭代器创build以来的更改。 批量操作只是多个单一的调用,而不是primefaces – 其他线程可能只观察其中的一些。
显然你只有在你的元素有一些总的顺序时才能使用它。 这看起来像高并发情况的理想候选者,对于不太大的集合(由于O(log n))。
4)对于ConcurrentHashMap
(和从它派生的集合):这里最基本的选项是(平均来说,如果你有一个好的和快速的hashCode()
)在O(1)中(但可能退化为O(n)),像HashMap / HashSet一样。 写入的performance是有限的(表分区,写访问将在所需的分区上同步),而读访问对于自己和写入线程是完全并发的(但是可能还没有看到当前正在进行的更改的结果书面)。 迭代器可能会也可能不会看到自创build以来的更改,批量操作也不是primefaces操作。 resize的速度很慢(对于HashMap / HashSet),因此通过估计创build时所需的大小(并使用大约1/3的大小,因为它在3/4满时resize)来尝试避免这种情况。
当你有大的集合,一个好的(快速的)散列函数,并且可以在创build映射之前估计集合大小和需要的并发性时,使用它。
5)有其他的并发映射实现可以在这里使用吗?
可以通过使用AtomicReference<Set>
将HashSet
的contains()
性能与CopyOnWriteArraySet
的并发性相关的属性相结合,并replace每个修改的整个集合。
实施草图:
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没有帮助,你可能应该find一本关于数据结构的书或文章。 乍看上去:
- CopyOnWriteArraySet每次改变集合时都会创build一个新的底层数组副本,因此写入速度慢,迭代器快速且一致。
- Collections.synchronizedSet()使用老派的同步方法调用来创buildSet线程安全。 这将是一个低性能的版本。
- ConcurrentSkipListSet提供具有不一致批处理操作(addAll,removeAll等)和迭代器的高性能写操作。
- Collections.newSetFromMap(新的ConcurrentHashMap())具有ConcurrentHashMap的语义,我相信它不一定是读或写的优化,但像ConcurrentSkipListSet一样,批处理操作也不一致。