并发容器(一) — 综述

一、概述

在开发过程中,经常会使用到一些数据结构,如散列表(HashMap)、队列(Queue)等,但是有一部分数据结构是线程不安全的,即在并发场景下,数据的获取和存储会出现问题。

为了解决这种问题,JDK提供了几个并发安全的数据结构,如下图所示:
在这里插入图片描述

由上图可知,JDK中提供的并发容器有:

  1. ConcurrentHashMap:分片加锁实现高性能散列表。
  2. ConcurrentLinkedQueue:采用CAS算法实现的非阻塞队列。
  3. BlockingQueue:使用锁对象实现的阻塞队列。

实现一个线程安全的队列有两种方式:

  1. 方式1:使用阻塞算法,使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。
  2. 方式2:使用非阻塞算法 (CAS)。非阻塞的实现方式是使用循环CAS的方式来实现。

ConcurrentHashMapConcurrentLinkedQueue 将单独介绍。本文主要讲解几种阻塞队列(BlockingQueue )的特点。


二、阻塞队列的基本操作

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列,这两个操作支持阻塞的插入和移除方法。

  1. 支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满的状态。
  2. 支持阻塞的移除方法:当队列为空时,获取元素的线程会一致等待队列变为非空的状态。

阻塞队列的应用场景:

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是
从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

阻塞队列内提供了4中 API 处理方式,如下图所示:
在这里插入图片描述

  1. 抛出异常:
    1. add(e):当队列满时,如果往队列里插入元素,会抛异常。
    2. remove():当队列空时,如果从队列里获取元素,会抛出异常。
    3. element():当队列空时,如果从队列里获取元素,会抛出异常。
  2. 返回特殊值:
    1. offer(e):当队列满时,如果往队列里插入元素,会返回 false 表示插入失败。
    2. poll():当队列空时,如果从队列里获取元素,会返回 null。
    3. peek():当队列空时,如果从队列里获取元素,会返回 null。
  3. 一直阻塞:
    1. put(e):当队列满时,如果往队列里插入元素,队列会阻塞当前线程,直到队列可用或者响应中断退出。
    2. take():当队列空时,如果从队列里获取元素,队列会阻塞当前线程,直到队列不为空。
  4. 超时退出:
    1. offer(time, unit): 当队列满时,如果往队列里插入元素,队列会阻塞当前线程一段时间,如果超过了指定的时间,当前线程就会退出。
    2. poll(time, unit):当队列空时,如果从队列里获取元素,队列会阻塞当前线程一段时间,如果超过了指定的时间,当前线程就会退出。

三、几种常见的阻塞队列

JDK1.7 提供了7个阻塞队列:

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  2. LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  3. PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  4. DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  5. SynchronousQueue:一个不存储元素的阻塞队列。
  6. LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
  7. LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

1. ArrayBlockingQueue

  1. ArrayBlockingQueue 是一个用数组实现有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。
  2. 默认情况下使用非公平锁进行队列访问 (公平锁会降低吞吐量)。

2. LinkedBlockingQueue

  1. LinkedBlockingQueue 是一个用链表实现有界阻塞队列。此队列按照先进先出的原则对元素进行排序。
  2. 此队列的默认和最大长度为Integer.MAX_VALUE。

3. PriorityBlockingQueue

  1. PriorityBlockingQueue 是一个支持优先级无界阻塞队列。
  2. 默认情况下元素采取自然顺序升序排列。
  3. 支持自定义类实现compareTo()方法来指定元素排序规则。
  4. 同优先级元素,PriorityBlockingQueue不能保证其顺序。

4. DelayQueue

  1. DelayQueue 是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。
  2. 队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。
  3. 只有在延迟期满时才能从队列中提取元素。

DelayQueue 的应用场景:

  1. 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
  2. 定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行。

5. SynchronousQueue

  1. SynchronousQueue 是一个不存储元素的阻塞队列。
  2. 每一个put操作必须等待一个take操作,否则不能继续添加元素。
  3. 默认情况下线程采用非公平性策略访问队列。

优势: SynchronousQueue 的吞吐量高于 LinkedBlockingQueueArrayBlockingQueue

6. LinkedTransferQueue

LinkedTransferQueue 是一个由链表结构组成的无界阻塞TransferQueue队列。

7. LinkedBlockingDeque

  1. LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列。
    双向队列:指可以从队列的两端插入和移出元素。
  2. 双向阻塞队列可以运用在 工作窃取 模式中。

四、阻塞队列的实现原理

如果队列是空的,消费者会一直等待,当生产者添加元素时,消费者是如何知道当前队列
有元素的呢?

可以使用 Condition 接口的等待通知模式来实现。具体请参考:Lock(五) — LockSupport 和 Condition接口

发布了158 篇原创文章 · 获赞 26 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Love667767/article/details/104898035