并发容器是指在高并发应用程序的使用过程中,这些容器(数据结构)是线程安全的,而且在高并发的程序中运行它们会有高效的性能表现。
Blocking Queue是指其中的元素数量存在界限,当队列已满时(队列元素数量达到了最大容量的临界值),对队列进行写入操作的线程将被阻塞挂起,当队列为空时(队列元素数量达到了为0的临界值),对队列进行读取的操作线程将被阻塞挂起。BlockingQueue(LinkedTransferQueue除外)的内部实现主要依赖于显式锁Lock及其与之关联的Condition。
ArrayBlockingQueue是一个基于数组结构实现的FIFO阻塞队列,在构造该阻塞队列时需要指定队列中最大元素的数量(容量)。当队列已满时,若再次进行数据写入操作,则线程将会进入阻塞,一直等待直到其他线程对元素进行消费。当队列为空时,对该队列的消费线程将会进入阻塞,直到有其他线程写入数据。
常用方法:
PriorityBlockingQueue优先级阻塞队列是一个“无边界”阻塞队列。该队列会根据某种规则(Comparator)对插入队列尾部的元素进行排序,因此该队列将不会遵循FIFO(first-in-first-out)的约束。
LinkedBlockingQueue是“可选边界”基于链表实现的FIFO队列。LinkedBlockingQueue队列的边界可选性是通过构造函数来决定的,当我们在创建LinkedBlockingQueue对象时,使用的是默认的构造函数,那么该队列的最大容量将为Integer的最大值(所谓的“无边界”),当然也可以通过指定队列最大容量(有边界)的方式创建队列。
DelayQueue也是一个实现了BlockingQueue接口的“无边界”阻塞队列,对于存入DelayQueue中的元素是有一定要求的:元素类型必须是Delayed接口的子类,存入DelayQueue中的元素需要重写getDelay(TimeUnit unit)方法用于计算该元素距离过期的剩余时间,如果在消费DelayQueue时发现并没有任何一个元素到达过期时间,那么对该队列的读取操作会立即返回null值,或者使得消费线程进入阻塞。在DelayQueue中,元素也会根据优先级进行排序,这种排序可以是基于数据元素过期时间而进行的。
SynchronousQueue也是实现自BlockingQueue的一个阻塞队列,每一次对其的写入操作必须等待(阻塞)其他线程进行对应的移除操作,SynchronousQueue的内部并不会涉及容量、获取size,就连peek方法的返回值永远都将会是null,除此之外还有更多的方法在SynchronousQueue中也都未提供对应的支持。
尽管SynchronousQueue是一个队列,但是它的主要作用在于在两个线程之间进行数据交换,区别于Exchanger的主要地方在于(站在使用的角度)SynchronousQueue所涉及的一对线程一个更加专注于数据的生产,另一个更加专注于数据的消费,而Exchanger则更加强调一对线程数据的交换。
LinkedBlockingDeque是一个基于链表实现的双向(Double Ended Queue,Deque)阻塞队列,双向队列支持在队尾写入数据,读取移除数据;在队头写入数据,读取移除数据。LinkedBlockingDeque实现自BlockingDeque(BlockingDeque又是BlockingQueue的子接口),并且支持可选“边界”,与LinkedBlockingQueue一样,对边界的指定在构造LinkedBlockingDeque时就已经确定了。
TransferQueue是一个继承了BlockingQueue的接口,并且增加了若干新的方法。LinkedTransferQueue是TransferQueue接口的实现类,其定义为一个无界的队列,具有FIFO的特性。
在前面学习的BlockingQueue阻塞队列,为了保护共享数据的一致性,需要对共享数据的操作进行加锁处理(显式锁或者synchronized关键字),为了使得操作线程挂起和被唤醒,我们需要借助于对象监视器的wait/notify/notifyAll或者与显式锁关联的Condition。而ConcurrentQueue(并发队列)是一种可以不用关心临界值,操作该队列的线程也不会被挂起并且等待被其他线程唤醒,我们可以向该队列中插入或者获取数据,并且该队列是线程安全的。
注意:
Hashtable或者SynchronizedMap虽然是线程安全的,但是在多线程高并发的环境中,简单粗暴的排他式加锁方式效率并不是很高。鉴于Map是一个在高并发的应用环境中应用比较广泛的数据结构,自JDK 1.5版本起在Java中引入了ConcurrentHashMap并且在随后的JDK版本迭代中都在不遗余力地为性能提升做出努力,除了ConcurrentHashMap之外,在JDK 1.6版本中又引入了另外一个高并发Map的解决方案ConcurrentSkipListMap。
ConcurrentHashMap是一个支持高并发更新与查询的哈希表(基于HashMap)。在保证安全的前提下,进行检索不需要锁定。与hashtable不同,该类不依赖于synchronization去保证线程操作的安全。
ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上,其能够在O(log(n))时间内完成查找、插入、删除操作。
CopyOnWrite容器,简称COW,该容器的基本实现思路是在程序运行的初期,所有的线程都共享一个数据集合的引用。所有线程对该容器的读取操作将不会对数据集合产生加锁的动作,从而使得高并发高吞吐量的读取操作变得高效,但是当有线程对该容器中的数据集合进行删除或增加等写操作时才会对整个数据集合进行加锁操作,然后将容器中的数据集合复制一份,并且基于最新的复制进行删除或增加等写操作,当写操作执行结束以后,将最新复制的数据集合引用指向原有的数据集合,进而达到读写分离最终一致性的目的。
CopyOnWrite容器是一种读写分离的思想,读和写不同的容器,因此不会存在读写冲突,而写写之间的冲突则是由全局的显式锁Lock来进行防护的,因此CopyOnWrite常常被应用于读操作远远高于写操作的应用场景中
Java中提供了两种CopyOnWrite算法的实现类:
COW算法为解决高并发读操作提供了一种新的思路(读写分离),但是其仍然存在一些天生的缺陷,具体如下: