Java中线程安全的容器主要包括两类:
Vector、Hashtable,以及封装器类Collections.synchronizedList和Collections.synchronizedMap;
Java 5.0引入的java.util.concurrent包,其中包含并发队列、并发HashMap以及写入时复制容器。
依笔者看,早期使用的同步容器主要有两方面的问题:1)通过对方法添加synchronized关键字实现同步,这种粗粒度的加锁操作在synchronized关键字本身未充分优化之前,效率偏低;2)同步容器虽然是线程安全的,但在某些外部复合操作(例:若没有则添加)时,依然需要客户端加锁保证数据安全。因此,从Java 5.0以后,并发编程偏向于使用java.util.concurrent包(作者:Doug Lea)中的容器类,本文也将着重介绍该包中的容器类,主要包括:
阻塞队列
ConcurrentHashMap
写入时复制容器
一、阻塞队列在并发环境下,阻塞队列是常用的数据结构,它能确保数据高效安全的传输,为快速搭建高质量的多线程应用带来极大的便利,比如MQ的原理就是基于阻塞队列的。java.util.concurrent中包含丰富的队列实现,它们之间的关系如下图所示:
BlockingQueue、Deque(双向队列)继承自Queue接口;
BlockingDeque同时继承自BlockingQueue、Deque接口,提供阻塞的双向队列属性;
LinkedBlockingQueue和LinkedBlockingDeque分别实现了BlockingQueue和BlockingDeque接口;
DelayQueue实现了BlockingQueue接口,提供任务延迟功能;
TransferQueue是Java 7引入的,用于替代BlockingQueue,LinkedTransferQueue是其实现类。
下面对这些队列进行详细的介绍:
1.1 BlockingQueue与BlockingDeque阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:
在队列为空时,获取元素的线程会等待队列变为非空。
当队列满时,存储元素的线程会等待队列可用。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
阻塞队列提供了四种处理方法:
方法 抛出异常 返回特殊值 一直阻塞 超时退出插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用
抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException("Queue full")异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。
返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null
一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。
超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。
BlockingDeque在BlockingQueue的基础上,增加了支持双向队列的属性。如下图所示,相比于BlockingQueue的插入和移除方法,变为XxxFirst,XxxLast方法,分别对应队列的两端,既可以在头部添加或移除,也可以在尾部添加或移除。
1.2 LinkedBlockingQueue与LinkedBlockingDequeLinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE,按照先进先出的原则对元素进行排序。
首先看下LinkedBlockingQueue中核心的域:
static class Node<E> { E item; Node<E> next; Node(E x) { item = x; } } private final int capacity; private final AtomicInteger count = new AtomicInteger(); transient Node<E> head; private transient Node<E> last; private final ReentrantLock takeLock = new ReentrantLock(); private final Condition notEmpty = takeLock.newCondition(); private final ReentrantLock putLock = new ReentrantLock(); private final Condition notFull = putLock.newCondition();LinkedBlockingQueue和LinkedList类似,通过静态内部类Node<E>进行元素的存储;
capacity表示阻塞队列所能存储的最大容量,在创建时可以手动指定最大容量,默认的最大容量为Integer.MAX_VALUE;