Java 中不同类型的线程安全集

2022-08-31 07:28:13

在Java中似乎有很多不同的实现和方法来生成线程安全的集。一些示例包括

1) CopyOnWriteArraySet

2) Collections.synchronizedSet(Set set)

3) ConcurrentSkipListSet

4) Collections.newSetFromMap(new ConcurrentHashMap())

5) 以类似于 (4) 的方式生成的其他集合

这些示例来自并发模式:Java 6 中的并发集实现

有人可以简单地解释一下这些例子和其他例子的区别,优点和缺点吗?我很难理解和保持Java Std Docs中的所有内容。


答案 1
  1. 这是一个非常简单的实现 - 它基本上有一个数组中的元素列表,当更改列表时,它会复制数组。此时正在运行的迭代和其他访问继续使用旧数组,避免了读取器和写入器之间同步的必要性(尽管写入本身需要同步)。通常快速的集合运算(特别是)在这里非常慢,因为数组将在线性时间内被搜索。CopyOnWriteArraySetcontains()

    仅将其用于非常小的集合,这些集合将经常读取(迭代)并且很少更改。(Swing的听众集就是一个例子,但这些并不是真正的集,无论如何都应该只在EDT中使用。

  2. Collections.synchronizedSet将简单地将同步块包装在原始集的每个方法周围。不应直接访问原始集。这意味着该集合的两个方法不能同时执行(一个将阻塞,直到另一个完成) - 这是线程安全的,但如果多个线程正在使用该集合,您将没有并发性。如果使用迭代器,则在修改迭代器调用之间的集合时,通常仍需要在外部进行同步,以避免 ConcurrentModificationException。性能将类似于原始集的性能(但有一些同步开销,如果同时使用,则阻塞)。

    如果您只有较低的并发性,并且希望确保所有更改对其他线程立即可见,请使用此选项。

  3. ConcurrentSkipListSet是并发实现,在 O(log n) 中具有大多数基本操作。它允许并发添加/删除和读取/迭代,其中迭代可能会或可能不会告诉自创建迭代器以来的变化。批量操作只是多个单个调用,而不是以原子方式完成的 - 其他线程可能只观察其中的一些。SortedSet

    显然,只有当您对元素有一些总顺序时,您才能使用它。这看起来像是高并发情况的理想候选者,对于不太大的集合(因为O(log n))。

  4. 对于(以及从中派生的 Set):这里大多数基本选项是(平均而言,如果你有一个好的和快速的)在O(1)中(但是当许多键具有相同的哈希代码时,可能会退化为O(n),就像HashMap /HashSet一样。写入的并发性有限(表已分区,写入访问将在所需的分区上同步),而读取访问与自身和写入线程完全并发(但可能尚未看到当前正在写入的更改的结果)。迭代器自创建以来可能会或可能不会看到更改,并且批量操作不是原子操作。调整大小很慢(与HashMap / HashSet一样),因此您应该通过在创建时估计所需的大小来避免这种情况(并使用大约1/3的尺寸,因为它在3/4满时会调整大小)。ConcurrentHashMaphashCode()

    当您具有大型集,一个好的(且快速的)哈希函数并且可以在创建映射之前估计集大小和所需的并发性时,请使用此选项。

  5. 这里还有其他并发映射实现吗?


答案 2

可以通过在每次修改时使用和替换整个集合,将 的性能与并发相关的属性相结合。contains()HashSetCopyOnWriteArraySetAtomicReference<Set>

实现草图:

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;
            }
        }
    }

}

推荐