并发容器类图
Queue
Queue上的操作不会阻塞,如果队列为空,那么获取元素的操作将返回空值。虽然可以使用List来模拟Queue的行为--事实上正是通过LinkedList来实现Queue的,但Queue能够去掉List的随机访问请求,实现更高效的并发。
Queue有几种实现,包括ConcurrentLinkedQueue和PriorityQueue。
ConcurrentLinkedQueue
这是一个传统的先进先出队列。ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。该队列是非阻塞的,如果从一个空的ConcurrentLinkedQueue中获取值,会返回null。因此,ConcurrentLinkedQueue中元素不允许有空值。
PriorityQueue
这是一个(非并发的)优先权队列。
BlockingQueue
BlockingQueue扩展了Queue,增加了可阻塞的插入和获取操作。如果队列为空,那么获取元素的操作会阻塞知道队列中出现一个可用元素;如果队列已满,那么插入操作会阻塞知道队列出现可用空间。BlockngQueue是一个接口,Java提供了多种不同的实现类。
生产者-消费者模式
阻塞队列支持生产者-消费者模式。该模式将“找出需要完成的工作”和“执行工作”这两个过程分离开来,并把工作放入一个“待完成”的列表中以便在随后处理,而不是找出后立即处理。生产者-消费者模式简化了开发过程,因为它消除了生产者类和消费者类之间的代码依赖性。
双端队列(Deque & BlockingDeque)
Deque和BlockingDeque分别扩展了Queue和BlockingQueue。Deque是一个双端队列,实现了在队列头和队列尾的高效插入和移除。具体实现包括ArrayDeque和ArrayBlockingDeque。
工作密取模式
正如阻塞队列适用于“生产者-消费者”模式,双端队列适用于另一种模式--“工作密取”。在生产者-消费者模式中,所有消费者共享一个工作队列,而在工作密取中,每个消费者都各自有自己的一个双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其他消费者的双端队列末尾秘密地获取工作。工作密取模式比一般的生产者-消费者模式具有更高的可伸缩性,这是因为工作线程不会在单个共享的任务队列发生竞争。
CopyOnWriteArrayList & CopyOnWriteArraySet
CopyOnWriteArrayList用于替代同步List,在某些情况下提供了更好的并发性能,并且在迭代期间不需要对容器进行加锁或复制(类似地,CopyOnWriteArraySet用来代替同步Set)。
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
“写入时复制”容器的线程安全性在于:只要正确的发布一个事实不可变对象,那么在访问该对象时就不需要再进一步的同步。显然,每当修改时都会复制底层数组,这需要一定的开销,尤其是数组规模较大时。仅当迭代操作远远多于修改操作时,才应该使用“写入时复制”容器。
ConcurrentHashMap
与HashMap一样,ConcurrentHashMap也是一个基于散列的Map,但它使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。ConcurrentHashMap使用更细粒度的分段锁机制而不是将每一个方法都在同一个锁上同步。这种机制中,任意数量的读线程可以并发访问Map,执行读取的线程可以和执行写入的线程并发访问Map,并且一定数量的写入线程可以并发地修改Map。ConcurrentHashMap带来的结果是,在并发环境下将带来更高的吞吐量,在单线程环境中只损失非常小的性能。
ConcurrentHashMap与其他并发容器一起增强了同步容器类:他们提供的迭代器不会抛出ConcurrentModificationException,因此不需要在迭代的过程中加锁。返回的迭代器具有“弱一致性”,并非“即时失败”。
ConcurrentHashMap中没有实现对Map加锁已提供独占访问。在HashMap和synchronizedMap中获取Map的锁能防止其他线程访问这个Map。与HashMap和synchronizedMap相比,用ConcurrentHashMap来代替同步Map能进一步提高可伸缩性,只有在应用程序需要加锁Map以进行独占访问时,才应该放弃ConcurrentHashMap。
正如ConcurrentHashMap用于代替同步Map,Java6引入ConcurrentSkipListMap和ConcurrentSkipListSet来分别作为SortedMap和SortedSet的并发替代品。