Java 并发编程(四)并发容器
并发容器
Java 5.0 提供了多种并发容器来改进同步容器的性能。
同步容器是将所有对容器的访问都串行化,以实现他们的线程安全性。代价是严重降低并发行,当多个线程竞争容器的锁时,吞吐量将严重降低。
并发容器是针对多个线程并发访问设计的。 Java 5.0 增加了 ConcurrentHashMap ,用来替代同步且基于散列的 Map ,增加了 CopyOnWriteArrayList ,用于在遍历操作为主要操作的情况下替代同步的 List 。
Java 5.0 还增加了两中心的容器类型:Queue 和 BlockingQAueue。
Queue 用来临时保存待处理的元素。它提供了几种实现,包括 ConcurrentLinkedQueue,这是一个传统的先进先出队列。PriorityQueue,这是一个(非并发的)优先队列。Queue 上的操作不会阻塞,如果队列为空,那么获取元素的操作将返回空值。虽然可以用 List 来模拟 Queue 的行为--事实上,正是通过 LinkedList 来实现 Queue 的,但还需一个 Queue 的类,因为它能去掉 List 的随机访问需求,从而实现更高效的并发。
BlockingQueue 扩展了 Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作将一直阻塞,直到队列中出现了一个可用的元素。如果队列已满(对于有界队列),那么插入元素的操作将一直阻塞,直到队列中出现可用的空间。在生产者-消费者模式中,这是十分有用的
正如 ConcurrentHashMap 用于代替基于散列的同步 Map,Java 6 也引入了 ConcurrentSkipListMap 和 ConcurrentSkipListSet ,分别作为同步的 SortedMap 和 SortedSet 的并发替代品。(例如用 synchronizedMap 包装的 TreeMap 或 TreeSet)。
ConcurrentHashMap
基本结构上,ConcurrentHashMap 与 HashMap 一样,但它使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。它使用一种粒度更细的加锁机制来实现更大程度的共享,这种机制称为分段锁(Lock Striping)。
采用分段锁,任意数量的读取线程可以并发的访问 Map,执行读取操作的线程和执行写入操作的线程可以并发的访问 Map,并且一定数量的写入线程可以并发的修改Map。ConcurrentHashMap 带来的结果是,在并发访问环境下将实现更高的吞吐量,而在单线程环境中只损失非常小的性能。
ConcurrentHashMap 与其他并发容器一起增强了同步容器类:他们提供的迭代器不会抛出 ConcurrentModificationException ,因此不需要在迭代过程中对容器加锁。ConcurrentHashMap 返回的迭代器具有弱一致性,而非 “及时失败”。弱一致性的迭代器可以容忍并发的修改,当创建迭代器时会遍历已有的元素,并可以在迭代器被构造后将修改操作反映给容器。
尽管有这些改进,但仍然有一些需要权衡的因素。对于一些需要在整个Map 上进行计算的方法,例如 size 和 isEmpty ,这些方法的寓意被略微减弱了以反映容器的并发特性。由于 size 方法返回的结果在计算时可能已经过期了,它实际上只是一个估计值,因此允许 size 返回一个近似值而不是一个精确值。虽然这看上去有些令人不安,但事实上 size 和 isEmpty 这样的方法在并发环境下的用处很小,因为他们的返回值总是在不断变化。因此,这些操作的需求被弱化了,以换取对其他更重要操作的性能优化,包括 get、put、containsKey 和 remove等。
与 Hashtable 和 synchronizedMap 相比,ConcurrentHashMap 有更多的优势以及更少的劣势。因此在大多数情况下,用 ConcurrentHashMap 来代替同步 Map 能进一步提高代码的可伸缩性。只有当应用程序需要加锁Map 以进行独占访问时,才能放弃使用 ConcurrentHashMap。
CopyOnWriteArrayList
CopyOnWriteArrayList 用于替代同步 List,在某些情况下,它提供了更好的并发性能。在迭代器件不需要对容器进行加锁或复制。(类似 CopyOnWriteArraySet)。
“写入时复制(Copy-On-Write)” 容器的线程安全性在于,只要正确的发布一个事实不可变对象,那么访问该对象时就不需要进一步的同步。每次修改的时候,都会创建并重新发布一个新的容器副本,从而实现可变性。“写入时复制”容器的迭代器保留一个指向底层基础数组的引用,这个数组当前位于迭代器的起始位置,由于他不会被修改,因此在对其进行同步时只需确保数组内容的可见性。因此,多个线程可以对这个容器进行迭代,而不会彼此干扰或者修改容器的线程相互干扰。
显然,每当修改容器时都会复制底层数组,这需要一定的开销,特别是当容器的规模较大时。晋档跌打操作远远多于修改操作时,才应该使用“写入时复制”容器。这个准则很好的描述了许多事件通知系统:
在分发通知时需要迭代已注册监听器链表,并调用每一个监听器,在大多数情况下,注册和注销事件监听器的操作远少于接收时间通知的操作。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。