JUC中常见队列介绍及实现原理

本文纯粹为了解AQS框架与线程池后对队列知识原理的随手补充笔记。

前言

Java并发编程中总离不开线程池的管理和使用,而线程池的线程执行与阻塞机制只要依赖于其使用的队列。
Java并发包JUC下的队列主要分为以下两种:

  • 单端队列:队列只有一个入口一个出口,单端队列类直接实现Queue接口,常见的线程池都会使用单端队列作为其线程队列
  • 双端队列:支持在两端插入和删除元素的队列,继承了Queue接口,双端队列类直接实现该接口

JUC下常见的队列相关类简介如下:

  • BlockingQueue接口:阻塞队列的抽象,不接受空元素,当队列已满存元素或队列空取元素会发生阻塞
  • BlockingDeque接口:继承了BlockingQueueDequeue接口,阻塞双端队列的抽象,不接受空元素,当队列已满存元素或队列空取元素会发生阻塞
  • ArrayBlockingQueue:基于数组实现的有界阻塞队列,由于队列的特性所以不适合有双端实现
  • LinkedBlockingQueue:基于链表节点的可选有界阻塞队列,SingleThreadPool与FixedThreadPool线程池使用的队列
  • LinkedBlockingDeque:基于链表节点的可选有界阻塞双端队列
  • ScheduledThreadPoolExecutor.DelayedWorkQueue:ScheduledThreadPoolExecutor类的内部类,ScheduledThreadPool线程池使用的延迟队列,根据任务执行时间排序基于堆的数据结构的延迟队列
  • DelayQueue:延迟任务的无限制阻塞队列
  • SynchronousQueue:一个没有内部容量的同步队列,每个插入操作必须等待另一个线程进行相应的删除操作,反之亦然。CachedThreadPool线程池使用的队列

JUC-BlockingQueue-Hierarchy.png

ArrayBlockingQueue

ArrayBlockingQueue是基于数组实现的有界阻塞队列,此队列元素排序为FIFO,队列的头是在队列中等待时间最长的元素,队列的尾部是在队列中等待时间最短的元素。在队列的尾部插入新元素,队列检索操作在队列的头部获取元素。ArrayBlockingQueue的容量设置后就无法再更改,尝试将元素放入满了的队列将导致操作阻塞,从空队列中获取元素同样会阻塞。
ArrayBlockingQueue支持公平性策略,用于排序等待的生产者和消费者线程,默认为非公平策略,公平性设置为true的队列按FIFO顺序授予线程访问权。公平性通常会降低吞吐量,但会降低可变性并避免饥饿。

ArrayBlockingQueue主要的构造方法如下:

public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

ArrayBlockingQueue中的主要属性如下:

  • Object[] items:元素数组
  • int takeIndex:下次取元素的数组索引
  • int putIndex:下次设置元素的数组索引
  • int count:队列元素数
  • ReentrantLock lock:队列锁
  • Condition notEmpty:表示队列状态不为空,取元素的等待队列
  • Condition notFull:表示队列状态未满,存元素的等待队列

LinkedBlockingQueue

LinkedBlockingQueue是基于链表节点的可选有界阻塞队列。该队列元素排序为FIFO,队列的头是在队列中等待时间最长的元素,队列的尾部是在队列中等待时间最短的元素,这方面跟ArrayBlockingQueue相同。链接队列通常比基于数组的队列具有更高的吞吐量,但在大多数并发应用程序中的性能可预测性较差。
该队列默认构造的元素容量是无界(Integer.MAX_VALUE)的,一般建议使用有容量参数的方法构造,避免链表里元素过多导致OOM。

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

LinkedBlockingQueue中的主要属性如下:

  • Node<E> head:链表头节点
  • Node<E> last:链表尾节点
  • ReentrantLock takeLock:元素取锁
  • Condition notEmpty:表示队列状态不为空,取元素的等待队列
  • ReentrantLock putLock:元素存锁
  • Condition notFull:表示队列状态未满,存元素的等待队列

看完以上属性相信都可以推导出队列的大致工作流程了,代码中的实现流程具体如下:

  • 取元素:调用take()方法
    1.使用takeLock上锁,如果队列为空则使当前线程进入等待状态直到被notEmpty唤醒,不为空则进入步骤2
    2.元素出队,判断取出前队列元素数是否大于1,大于1则使用notEmpty唤醒等待取元素的线程
    3.takeLock解锁
    4.判断元素取出前队列数是否已满,如果未满则使用notFull发出队列不满的信号唤醒存元素的等待线程
public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}
  • 存元素:调用offer(E)方法
  1. 队列满就直接返回false,不空则进入步骤2
  2. 使用putLock上锁,元素入队,入队后如果队列未满使用notFull发出未满信号使存元素的线程继续执行无需等待
  3. putLock解锁
  4. 判断元素插入前队列数是否为0,如果为0则使用notEmpty发出队列不为空的信号唤醒取元素的等待线程
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    if (count.get() == capacity)
        return false;
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值