同步----线程安全的集合

  除了阻塞队列(BlockingQueue),java.util.concurrent提供了映射、有序集和队列的高效实现:ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetConcurrentLinkedQueue。这些集合使用复杂的算法,通过允许并发地访问数据结构的不同部分来使竞争极小化。
  与大多数集合不同,size方法不必在常量时间内操作。确定这样的集合当前的大小通常需要遍历

1.对并发散列映射的批操作

  Java SE 8为并发散列映射提供了批操作,即使有其他线程在处理映射,这些操作也能安全地执行。
  有3种不同的操作

  • 搜索
  • 归约
  • forEach
      每个操作都有4个版本
  • operationKeys:处理键
  • operationValues: 处理值
  • operation:处理键和值
  • operationEntries:处理Map.Entry对象
      以search方法为例,有以下版本:
U searchKeys(long threshold, BiFunction<? super K, ? extends U> f)
U searchValues(long threshold, BiFunction<? super K, ? extends U> f)
U search(long threshold, BiFunction<? super K, ? extends U> f)
U searchEntries(long threshold, BiFunction<<Map.Entry<K, V>>, ? extends U> f)

2.并发集视图

  假如想要的是一个大线程安全的集而不是映射,并没有一个ConcurrentHashSet类静态newKeySet方法生成一个Set<K>,这实际上是ConcurrentHashMap<K, Boolean>的一个包装器。(所有的映射值都为Boolean.TRUE,不过因为只是要把它用作一个集,所以并不关心具体的值。)

Set<String> words = ConcurrentHashMap.<String>newKeySet();

  如果原来有一个映射,keySet方法可以生成这个映射的键集。这个集是可变的,如果删除这个集的元素,这个键(以及相应的值)会从映射中删除。不过,不能向键集增加元素,因为没有相应的值可以增加。
  Java SE 8ConcurrentHashMap增加了第二个keySet方法,包含一个默认值,可以在为集增加元素时使用

Set<String> words = map.keySet(1L);
words.add("Java");
//如果"Java"在words中不存在,现在它会有一个值1。

3.写数组的拷贝

  CopyOnWriteArrayListCopyOnWriteArraySet线程安全的集合,其中所有的修改线程对底层数组进行复制。如果在集合上进行迭代的线程数超过修改线程数,这样的安排是很有用的。
  当构建一个迭代器的时候,它包含一个对当前数组的引用。如果数组后来被修改了,迭代器仍然引用旧数组,但是,集合的数组已经被替换了。因而,旧的迭代器拥有一致的(可能过时的)视图,访问它无须任何同步开销。

4.并行数组算法

  在Java SE 8中,Arrays类提供了大量并行化操作静态Arrays.parallelSort方法可以对一个基本类型值对象数组排序
  parallelSetAll方法会用由一个函数计算得到的值填充一个数组。这个函数接收元素索引,然后计算相应位置上的值。
  最后还有一个parallelPrefix方法,它会用对应一个给定结合操作的前缀累加结果替换各个数组元素。如果有足够多的处理器,这会远远胜过直接的线性计算。

5.同步包装器

  从Java的初始版本开始,VectorHashtable类就提供线程安全的动态数组散列表的实现。现在这些类被弃用了,取而代之的是ArrayListHashMap类。这些不是线程安全的,而集合库中提供了不同的机制。任何集合类都可以通过使用同步包装器(synchronization wrapper)变成线程安全的

List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E> ());
Map<K, V> synchHashMap = Collections.synchronizedMap(new HashMap<K, V> ());
//集合的方法使用锁加以保护,提供了线程安全访问。

  应该确保没有任何线程通过原始的非同步方法访问数据结构。最便利的方法是确保不保存任何指向原始对象的引用,简单地构造一个集合并立即传递给包装器。
  如果在另一个线程可能进行修改时要对集合进行迭代,仍然需要使用“客户端”锁定:

synchronized(synchHashMap) {
    Iterator<K> iter = synchHashMap.keySet().iterator();
    while (iter.hasNext())...;
}

  最好使用java.util.concurrent包中定义的集合,不使用同步包装器中。有一个例外经常被修改的数组列表。在那种情况下,同步的ArryaList可以胜过CopyOnWriteArrayList。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。