并发编程从入门到放弃系列开始和结束(八)

发布于 2022-6-13 17:40
浏览
0收藏

 

阻塞队列

并发编程中,队列是其中不可缺少的一环,其实前面在说到线程池的时候,就已经提及到了阻塞队列了,这里我们要一起看看 JUC 包下提供的这些队列。

并发编程从入门到放弃系列开始和结束(八)-开源基础软件社区

 阻塞队列

阻塞队列中的阻塞包含两层意思:

  1. 插入的时候,如果阻塞队列满,插入元素阻塞
  2. 删除/查询的时候,如果阻塞队列空,删除/查询元素阻塞
    下面列出队列的一些插入和删除元素的方法,一个个来说:

add:向队列尾部插入元素,插入成功返回 true,队列满则抛出IllegalStateException("Queue full")异常

offer:向队列尾部插入元素,队列满返回 false,否则返回 true,带超时的则是会阻塞,达到超时时间后返回

put:向队列尾部插入元素,队列满会一直阻塞

remove:删除队列头部元素,删除成功返回 true,队列空则抛出NoSuchElementException异常

poll:删除队列头部元素,删除成功返回队列头部元素,队列空返回null,带超时的则是会阻塞,达到超时时间后返回

take:删除队列头部元素,队列空会一直阻塞

element:查询队列头部元素,并且返回,队列空则抛出NoSuchElementException异常

peek:查询队列头部元素,并且返回

 并发编程从入门到放弃系列开始和结束(八)-开源基础软件社区
ArrayBlockingQueue
ArrayBlockingQueue 从名字就知道,基于数组实现的有界阻塞队列,基于AQS支持公平和非公平策略。

还是看构造函数吧,可以传入初始数组大小,一旦设置之后大小就不能改变了,传参可以支持公平和非公平,最后一个构造函数可以支持传入集合进行初始化,但是长度不能超过 capacity,否则抛出ArrayIndexOutOfBoundsException异常。

public ArrayBlockingQueue(int capacity);
public ArrayBlockingQueue(int capacity, boolean fair);
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c);

这个其实在上面介绍 Condition 的时候我们就已经实现过他了,这里就不再说了,可以参考上面 Condition 的部分。

LinkedBlockingQueue
LinkedBlockingQueue 基于链表实现的有界阻塞队列。

使用无参构造函数则链表长度为 Integer.MAX_VALUE,另外两个构造函数和 ArrayBlockingQueue 差不多。

public LinkedBlockingQueue();
public LinkedBlockingQueue(int capacity);
public LinkedBlockingQueue(Collection<? extends E> c);

我们可以看看 put 和 take 的源码。

  1. 首先加锁中断
  2. 然后判断如果达到了队列的最大长度,那么就阻塞等待,否则就把元素插入到队列的尾部
  3. 注意这里和 ArrayBlockingQueue 有个区别,这里再次做了一次判断,如果队列没满,唤醒因为 put 阻塞的线程,为什么要做判断,因为他们不是一把锁
  4. 最后的逻辑是一样的,notEmpty 唤醒
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }
    
    private void enqueue(Node<E> node) {
       // assert putLock.isHeldByCurrentThread();
       // assert last.next == null;
       last = last.next = node;
    }
    
    private void signalNotEmpty() {
       final ReentrantLock takeLock = this.takeLock;
       takeLock.lock();
       try {
         notEmpty.signal();
       } finally {
         takeLock.unlock();
       }
    }​

take的逻辑也是非常类似啊。

  1. 加锁中断
  2. 判断队列是不是空了,空了的话就阻塞等待,否则就从队列移除一个元素
  3. 然后再次做一次判断,队列要是不空,就唤醒阻塞的线程
  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;
    }
    
    private E dequeue() {
       // assert takeLock.isHeldByCurrentThread();
       // assert head.item == null;
       Node<E> h = head;
       Node<E> first = h.next;
       h.next = h; // help GC
       head = first;
       E x = first.item;
       first.item = null;
       return x;
    }
    
    private void signalNotFull() {
       final ReentrantLock putLock = this.putLock;
       putLock.lock();
       try {
         notFull.signal();
       } finally {
         putLock.unlock();
       }
    }​

    PriorityBlockingQueue

PriorityBlockingQueue 是支持优先级的无界阻塞队列,默认排序按照自然排序升序排列。

几个构造函数,无参构造函数初始容量为11,可以自定义,也可以在创建的时候传入 comparator 自定义排序规则。

public PriorityBlockingQueue();
public PriorityBlockingQueue(int initialCapacity);
public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator);
public PriorityBlockingQueue(Collection<? extends E> c);

直接看 put 和 take 方法吧,后面都这样,其他的就忽略好了,找到 put 之后,发现直接就是调用的 offer,那我们就直接看 offer 的实现。

  1. 首先还是加锁,然后看当前元素个数是否达到了数组的上限,到了就调用 tryGrow 去扩容。
  2. 看是否实现了 Comparator 接口,是的话就用 Comparator 去排序,否则就用 Comparable 去比较,如果两个都没有,会报错
  3. notEmpty 唤醒,最后解锁

 

文章转自公众号:艾小仙

分类
标签
已于2022-6-13 17:40:29修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐