java中提供了丰富的集合类操作,大概可以分为无序结合Set,有序集合List和无序键值对集合Map。Java5之后又新增了队列操作集合Queue。Java1.5之后新增了线程安全的集合操作类,阻止在java.util.concurrent包中。本文仅仅探讨该包下面的线程安全的结合操作类。
先看下concurrent包下面线程安全类的类图结构:
1.CopyOnWriteArraySet类
CopyOnWriteArrayList类的底层是通过CopyOnWriteArrayList来实现的。因此它与CopyOnWriteArrayList有相同的性质:
2.CopyOnWriteArrayList类
CopyOnWriteArrayList是一个线程安全类的变体,所有的写操作都是通过对底层数组的复制来完成的,因此该集合类的写操作代价非常高昂。但是CopyOnWriteArrayList也正是通过上述快照的方式来实现了读操作的高并发性。因为底层代码的实现是通过一个数组来存储元素,因此集合总是返回调用方法那一刻数组中对应的元素。对于其他线程的并发写操作而忽视。
在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。"快照"风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内绝不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。自创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。不支持迭代器上更改元素的操作(移除、设置和添加)。这些方法将抛出 UnsupportedOperationException。
3.ConcurrentLinkedQueue
一个基于链接节点的、无界的、线程安全的队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部是队列中时间最长的元素。队列的尾部是队列中时间最短的元素。新的元素插入到队列的尾部,队列检索操作从队列头部获得元素。当许多线程共享访问一个公共collection 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许 null 元素。
此实现采用了有效的"无等待 (wait-free)"算法。与大多数 collection 不同,size 方法不是 一个固定时间的操作。由于这些队列的异步特性,确定当前元素的数量需要遍历这些元素。
且该队列集合的操作是非阻塞的。
4.BlockingQueue接口
BlockingQueue不光实现了一个完整队列所具有的基本功能,同时在多线程环境下,他还自动管理了多线间的自动等待于唤醒功能,从而使得程序员可以忽略这些细节,关注更高级的功能。
BlockingQueue的特点:
BlockingQueue的常用方法:
add(E o):将指定的元素插入到队列中(如果队列仍有空间),执行成功之后返回true,否则则直接抛出IllegalSatateException.
offer(E o):将指定的元素插入到队列中,成功则返回true,否则返回false
put(E o):指定的元素插入的队列中,如果队列中没有空间则一直等待。(阻塞的)
poll(long timeout, TimeUnit unit):检索并移除此队列的头部,如果此队列中没有任何元素,则等待指定等待的时间(如果有必要)。
take():检索并移除此队列的头部,如果此队列不存在任何元素,则一直等待。(阻塞的)
4.1 ArrayBlockingQueue(有界缓存,FIFO,支持公平访问策略)
(1)一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。
(2)队列的头部 是在队列中存在时间最长的元素,队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列检索操作则是从队列头部开始获得元素。
(3)这是一个典型的"有界缓存区",固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致放入操作受阻塞;试图从空队列中检索元素将导致类似阻塞。
(4)此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了"不平衡性"。
4.2 LinkedBlockingQueue(无界)
(1)基于链表的阻塞队列,容量是任意的。提供可选的指定容量的构造方法来防止队列过度扩张,如果未指定容量,则它的容量等于Integer.MAX_VALUE。除非插入的节点已经超过队列容量,否则每次插入都会动态的插入一个新的节点。
(2)该队列在存储元素的时候也是按照FIFO原则排序。队列的头部元素在队列中呆时间最长,尾部元素则时间最短,每次插入元素会插入到队列的尾部。
4.3 DelayQueue(无界)
(1)Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。
(2)该队列的头部 是延迟期满后保存时间最长的 Delayed 元素(不是最先放入的元素)。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。
(3)当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满。
(4)该队列的元素必须实习那Delayed接口。
4.4 PriorityBlockingQueue(无界)
(1)一个无界的阻塞队列,它使用与类 PriorityQueue 相同的顺序规则:基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),并且提供了阻塞检索的操作。
(2)虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会失败(导致 OutOfMemoryError)。
(3)此类不允许使用 null 元素。
(4)依赖自然顺序的优先级队列也不允许插入不可比较的对象(因为这样做会抛出 ClassCastException)。
4.5 SynchronousQueue
(1)一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。
(2)不能在同步队列上进行 peek,因为仅在试图要取得元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)添加元素;也不能迭代队列,因为其中没有元素可用于迭代。
(3)队列的头 是尝试添加到队列中的首个已排队线程元素;如果没有已排队线程,则不添加元素并且头为 null。对于其他 Collection 方法(例如 contains),SynchronousQueue 作为一个空集合。此队列不允许 null 元素。
(4)同步队列非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。
(5)对于正在等待的生产者和使用者线程而言,此类支持可选的公平排序策略。默认情况下不保证这种排序。但是,使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。公平通常会降低吞吐量,但是可以减小可变性并避免得不到服务。