Java并发(一)——线程安全的容器(上)

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方法,分别对应队列的两端,既可以在头部添加或移除,也可以在尾部添加或移除。

BlockingDeque插入/移除方法

1.2 LinkedBlockingQueue与LinkedBlockingDeque

LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为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;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwpds.html