1.并发集合类

java.util.concurrent 包中引入了一些线程安全的集合对象,它们被称为并发集合。这些对象通常可以作为同步集合的替代品,它们与常用的非线程安全集合对象之间的对应关系如下表所示。

  • ConcurrentLinkedQueue是Queue接口的一个线程安全实现类,它相当于LinkedList(也是 Queue 接口的一个实现类)的线程安全版,可以作为Collections.synchronizedList(new LinkedList()) 的替代品。ConcurrentLinkedQueue 内部访问其共享状态变量(如队首指针和队尾指针)的时候并不借助锁,而是使用 CAS 操作来保障线程安全的 。 因此,ConcurrentLinkedQueue 是非阻塞的, 其使用不会导致当前线程被暂停,因此也就避免了上下文切换的开销 。 ConcurrentLinkedQueue 所使用的遍历方式是准实时。与BlockingQueue的实现类相比,ConcurrentLinkedQueue更适合于更新操作和遍历操作并发的场景,比如一个(或者多个)线程往/从队列中添加/删除元素,而另外一个(或者多个)线程则对相应队列进行遍历操作 。 而 BlockingQueue 的实现类(如 ArrayBlockingQueue)更适合于多个线程并发更新同一队列的场景,比如在生产者—消费者模式中生产者线程往队列中添加元素(产品),而消费者线程从队列中移除(消费)元素 。
  • ConcurrentHashMap 是 Map 接口的一个线程安全实现类,它相当于 HashMap (也是Map 接口的一个实现类)的线程安全版,可以作为 Hashtable 和Collections.synchronizedMap(new HashMap())的替代品。ConcurrentHashMap 内部使用了粒度极小的锁来保障其线程安全。 ConcurrentHashMap 的读取操作(如调用ConcurrentHashMap . get 方法)基本上不会导致锁的使用 。 另外,默认情况下 ConcurrentHashMap 可以支持 16 个并发更新线程,即这些线程可以在不导致锁 (ConcurrentHashMap 内部使用的锁)的争用情况下进行并发更新。因此,ConcurrentHashMap 可以支持比较高的并发性,并且其锁的开销一般比较小。ConcurrentHashMap 中一个构造器支持的 concurrencyLevel 参数可以使我们调整ConcurrentHashMap支持的并发更新线程数。这个值越大表示相应的开销越大,越小表示它越可能导致并发更新时出现锁的争用。因此,concurrencyLevel 的值要调整也必须是根据实际需要来权衡 。 ConcurrentHashMap 所使用的遍历方式是准实时 。
  • CopyOnWriteArrayList 是 List 接口的一个线程安全实现类,它相当于 ArrayList (也是List 接口的一个实现类)的线程安全版。CopyOnWriteArrayList内部会维护一个实例变量array用于引用一个数组。该数组用于存储列表的各个元素。CopyOnWriteArrayList 的更新操作(添加、修改和删除) 是通过创建一个新的数组 newArray, 并把老的数组 (array 当前所引用的数组)的内容复制到 newArray, 然后对 newArray 进行更新并将 array 引用指向 newArray。因此,array所引用的数组相当于当前CopyOnWriteArrayList实例的一个快照,而对CopyOnWriteArrayList 的更新操作所导致的对象的复制(主要是对象引用的复制)的开销相当于这个快照的间接开销。CopyOnWriteArrayList 所使用的遍历方式就是快照。因此,CopyOnWriteArrayList 适用于遍历操作远比更新操作(增加、删除和修改)频繁或者不希望在遍历的时候加锁的场景。而在其他场景下,我们可能仍然要考虑使用Collections.synchronizedList(new ArrayList()) 。
  • CopyOnWriteArraySet是 Set 接口的一个线程安全实现类,它相当于 HashSet (也是 Set 接口的一个实现类)的线程安全版。CopyOnWriteArraySet 内部实现使用了一个 CopyOnWriteArrayList实例,因此 CopyOnWriteArraySet 的适用场景与CopyOnWriteArrayList相似。

2.遍历方式

  • 一种是对待遍历对象的快照进行遍历。快照是在 Iterator 实例被创建的那一刻待遍历对象内部结构的一个只读副本(对象),它反映了待遍历集合的某一时刻(即 Iterator 实例被创建的那一刻)的状态(不包括集合元素的状态)。由于对同一个并发集合进行遍历操作的每个线程会得到各自的一份快照,因此快照相当于这些线程的线程特有对象 。 所以,这种方式下进行遍历操作的线程无须加锁就可以实现线程安全。另外,由于快照是只读的,因此这种遍历方式所返回的 Iterator 实例是不支持 remove 方法的 。 这种方式的优点是遍历操作和更新操作之间互不影响(因为各自操作的是不同的对象),缺点是当被遍历的集合比较大时,创建快照的直接或者间接开销会比较大。CopyOnWriteArrayList 和 CopyOnWriteArraySet 就使用这种遍历方式。
  • 另一种是对待遍历对象进行准实时的遍历。所谓准实时是指遍历操作不是针对待遍历对象的副本进行的,但又不借助锁来保障线程安全,从而使得遍历操作可以与更新操作并发进行。并且,遍历过程中其他线程对被遍历对象的内部结构的更新(比如删除了一个元素)可能会(也可能不会)被反映出来。这种遍历方式所返回的 Iterator 实例可以支持 remove 方法。ConcurrentLinkedQueue 和ConcurrentHashMap 等并发集合就采用这种遍历方式。由于 Iterator 是被设计用来一次只被一个线程使用的,因此如果有多个线程需要进行遍历操作,那么这些线程之间是不适宜共享同一个 Iterator 实例的!