java并发(十九)——阻塞队列:BlockingQueue

本文详细介绍了Java中八种阻塞队列的工作原理,包括ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue、SynchronousQueue、LinkedTransferQueue、LinkedBlockingDeque等。分析了每种队列的特点、应用场景及内部实现机制。

Java 中总的算起来有 8 种阻塞队列。

在这里插入图片描述

ArrayBlockingQueue

ArrayBlockingQueue,一个由数组实现的有界阻塞队列。该队列采用FIFO的原则对元素进行排序添加的。ArrayBlockingQueue为有界且固定,其大小在构造时由构造函数来决定,确认之后就不能再改变了。ArrayBlockingQueue支持对等待的生产者线程和使用者线程进行排序的可选公平策略,但是在默认情况下不保证线程公平的访问,在构造时可以选择公平策略(fair = true)。公平性通常会降低吞吐量,但是减少了可变性和避免了“不平衡性”。

成员变量

先看ArrayBlockingQueue的定义:

    public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable {
        private static final long serialVersionUID = -817911632652898426L;
        final Object[] items;
        int takeIndex;
        int putIndex;
        int count;
        // 重入锁
        final ReentrantLock lock;
        // notEmpty condition
        private final Condition notEmpty;
        // notFull condition
        private final Condition notFull;
        transient ArrayBlockingQueue.Itrs itrs;
    }

可以清楚地看到ArrayBlockingQueue继承AbstractQueue,实现BlockingQueue接口。看过java.util包源码的同学应该都认识AbstractQueue,改类在Queue接口中扮演着非常重要的作用,该类提供了对queue操作的骨干实现(具体内容移驾其源码)。BlockingQueue继承java.util.Queue为阻塞队列的核心接口,提供了在多线程环境下的出列、入列操作,作为使用者,则不需要关心队列在什么时候阻塞线程,什么时候唤醒线程,所有一切均由BlockingQueue来完成。

ArrayBlockingQueue内部使用可重入锁ReentrantLock + Condition来完成多线程环境的并发操作。

  • items,一个定长数组,维护ArrayBlockingQueue的元素。
  • takeIndex,int,为ArrayBlockingQueue对首位置。
  • putIndex,int,ArrayBlockingQueue对尾位置。
  • count,元素个数。
  • lock,锁,ArrayBlockingQueue出列入列都必须获取该锁,两个步骤公用一个锁。
  • notEmpty,出列条件。
  • notFull,入列条件。
入队

ArrayBlockingQueue提供了诸多方法,可以将元素加入队列尾部。

  • add(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException。
  • offer(E e) :将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
  • offer(E e, long timeout, TimeUnit unit) :将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。
  • put(E e) :将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。

方法较多,我们就分析一个方法:add(E e):

    public boolean add(E e) {
        return super.add(e);
    }

    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

add方法调用offer(E e),如果返回false,则直接抛出IllegalStateException异常。offer(E e)为ArrayBlockingQueue实现:

   public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)
                return false;
            else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

方法首先检查是否为null,然后获取lock锁。获取锁成功后,如果队列已满则直接返回false,否则调用enqueue(E e),enqueue(E e)为入列的核心方法,所有入列的方法最终都将调用该方法在队列尾部插入元素:

    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

该方法就是在putIndex(对尾)为知处添加元素,最后使用notEmpty的signal()方法通知阻塞在出列的线程(如果队列为空,则进行出列操作是会阻塞)。

出队

ArrayBlockingQueue提供的出队方法如下:

  • poll() :获取并移除此队列的头,如果此队列为空,则返回 null。
  • poll(long timeout, TimeUnit unit) :获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。
  • remove(Object o) :从此队列中移除指定元素的单个实例(如果存在)。
  • take() :获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。

poll()

    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

如果队列为空返回null,否则调用dequeue()获取列头元素:

   private E dequeue() {
        final Object[] items = this.items;
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

该方法主要是从列头(takeIndex 位置)取出元素,同时如果迭代器itrs不为null,则需要维护下该迭代器。最后调用notFull.signal()唤醒入列线程。

take()
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

take()与poll()存在一个区别就是count == 0 时的处理,poll()直接返回null,而take()则是在notEmpty上面等待直到被入列的线程唤醒。

LinkedBlockingQueue

LinkedBlockingQueue是一个使用链表实现的阻塞队列,支持多线程并发操作,可保证数据的一致性。与ArrayBlockingQueue相同的是,LinkedBlockingQueue也实现了元素“先进先出(FIFO)”规则,也使用ReentrantLock来保证数据的一致性;与ArrayBlockingQueue不同的是,LinkedBlockingQueue通常被认为是“无界”的,在默认情况下LinkedBlockingQueue的链表长度为Integer.MAX_VALUE。

成员变量

对于ArrayBlockingQueue来说,当队列在进行入队和出队时,永远只能有一个操作被执行。因为该队列只有一把锁,所以在多线程执行中并不允许同时出队和入队。与ArrayBlockingQueue不同的是,LinkedBlockingQueue拥有两把锁,一把读锁,一把写锁,可以在多线程情况下,满足同时出队和入队的操作。

在ArrayBlockingQueue中,由于出队入队使用了同一把锁,无论元素增加还是减少,都不会影响到队列元素数量的统计,所以使用了int类型的变量作为队列数量统计。但是,在LinkedBlockingQueue中则不同。上面说了,在LinkedBlockingQueue中使用了2把锁,在同时出队入队时,都会涉及到对元素数量的并发修改,会有线程安全的问题。因此,在LinkedBlockingQueue中使用了原子操作类AtomicInteger,底层使用CAS(compare and set)来解决数据安全问题。

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    
    //队列容量大小,默认为Integer.MAX_VALUE
    private final int capacity;

    //队列中元素个数:(与ArrayBlockingQueue的不同)
    //出队和入队是两把锁
    private final AtomicInteger count = new AtomicInteger(0);

    //队列--头结点
    private transient Node<E> head;

    //队列--尾结点
    private transient Node<E> last;

    //与ArrayBlockingQueue的不同,两把锁
    //读取锁
    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在内部是维持着一个队列,所以有一个头结点head和一个尾结点last,并且在内部维持着两把锁,takeLock用于出队列,putLock用于入队列,还有与两把锁关联的condition对象。

链表结点

由于LinkedBlockingQueue是链表结构,所以必然会有结点存在。结点中,保存这元素的值,以及本结点指向下一个结点的指针。

//队列存储元素的结点(链表结点):
static class Node<E> {
    //队列元素:
    E item;

    //链表中指向的下一个结点
    Node<E> next;

    //结点构造:
    Node(E x) { item = x; }
}
构造函数

之前,我们说了LinkedBlockingQueue可以称为是无界队列,为什么是无界的,就是因为LinkedBlockingQueue的默认构造函数中,指定的队列大小为Integer.MAX_VALUE = 2147483647,想必没有哪个应用程序能达到这个数量。

在初始化中,LinkedBlockingQueue的头尾结点中的元素被置为null;

//默认构造函数:
public LinkedBlockingQueue() {
    //默认队列长度为Integer.MAX_VALUE
    this(Integer.MAX_VALUE);
}

//指定队列长度的构造函数:
public LinkedBlockingQueue(int capacity) {
    //初始化链表长度不能为0
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    //设置头尾结点,元素为null
    last = head = new Node<E>(null);
}
插入元素(入队)

LinkedBlockingQueue的插入获取和ArrayBlockingQueue基本类似,都包含有阻塞式和非阻塞式。

put(E e)是阻塞式插入,如果队列中的元素与链表长度相同,则此线程等待,直到有空余空间时,才执行。

//向队列尾部插入元素:队列满了线程等待
public void put(E e) throws InterruptedException {
    //不能插入为null元素:
    if (e == null) throw new NullPointerException();
    int c = -1;
    //创建元素结点:
    Node<E> node = new Node(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    //加插入锁,保证数据的一致性:
    putLock.lockInterruptibly();
    try {
        //当队列元素个数==链表长度
        while (count.get() == capacity) {
            //插入线程等待:
            notFull.await();
        }
        //插入元素:
        enqueue(node);
        //队列元素增加:count+1,但返回+1前的count值:
        c = count.getAndIncrement();
        //容量还没满,唤醒生产者线程
        // (例如链表长度为5,此时第五个元素已经插入,c=4,+1=5,所以超过了队列容量,则不会再唤醒生产者线程)
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        //释放锁:
        putLock.unlock();
    }
    //当c=0时,即意味着之前的队列是空队列,消费者线程都处于等待状态,需要被唤醒进行消费
    if (c == 0)
        //唤醒消费者线程:
        signalNotEmpty();
}

offer(E e)是非阻塞式插入,队列中的元素与链表长度相同时,直接返回false,不会阻塞线程。

//向队列尾部插入元素:返回true/false
public boolean offer(E e) {
    //插入元素不能为空
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    //如果队列元素==链表长度,则直接返回false
    if (count.get() == capacity)
        return false;
    int c = -1;
    //创建元素结点对象:
    Node<E> node = new Node(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;
}
获取元素(出队)

take():阻塞式出队,获取队列头部元素,如果队列中没有元素,则此线程的等待,直到队列中有元素才执行。

//从队列头部获取元素,并返回。队列为null,则一直等待
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();
        //减少队列元素-1,返回count减少前的值;
        c = count.getAndDecrement();
        //队列中还有可以消费的元素,唤醒其他消费者线程
        if (c > 1)
            notEmpty.signal();
    } finally {
        //释放锁:
        takeLock.unlock();
    }
    //队列中出现了空余元素,唤醒生产者进行生产。
    // (链表长度为5,队列在执行take前有5个元素,执行到此处时候有4个元素了,但是c的值还是5,所以会进入到if中来)
    if (c == capacity)
        signalNotFull();
    return x;
}

poll():非阻塞式出队,当队列中没有元素,则返回null。

//获取头部元素,并返回。队列为空,则直接返回null
public E poll() {
    final AtomicInteger count = this.count;
    //如果队列中还没有元素,则直接返回 null
    if (count.get() == 0)
        return null;
    E x = null;
    int c = -1;
    final ReentrantLock takeLock = this.takeLock;
    //加锁,保证数据的安全
    takeLock.lock();
    try {
        //此时在判断,队列元素是否大于0
        if (count.get() > 0) {
            //移除队头元素
            x = dequeue();
            //减少队列元素个数
            c = count.getAndDecrement();
            //此时队列中,还有1个元素,唤醒消费者线程继续执行
            if (c > 1)
                notEmpty.signal();
        }
    } finally {
        //释放锁:
        takeLock.unlock();
    }
    //队列中出现了空余元素,唤醒生产者进行生产。
    // (链表长度为5,队列在执行take前有5个元素,执行到此处时候有4个元素了,但是c的值还是5,所以会进入到if中来)
    if (c == capacity)
        signalNotFull();
    return x;
}
ArrayBlockingQueue与LinkedBlockingQueue对比

ArrayBlockingQueue底层基于数组实现,需要使用者指定队列长度,是一个不折不扣的有界队列。LinkedBlockingQueue底层基于链表实现,无需使用者指定队列长度(可自定义),当使用默认大小时候,是一个无界队列。ArrayBlockingQueue由于默认必须设置队列长度,所以在使用时会能更好的预测系统性能;而LinkedBlockingQueue默认无参构造,无需指定队列长度,所以在使用时一定要多加注意,当队列中元素短时间内暴增时,可能会对系统产生灾难性影响。

但是,LinkedBlockingQueue的一大优点也是ArrayBlockingQueue所不具备的,那么就是在多个CPU的情况下,LinkedBlockingQueue可以做到同一时刻既消费、又生产。故LinkedBlockingQueue的性能也要优于ArrayBlockingQueue。

LinkedBlockingDeque

前面的BlockingQueue都是单向的FIFO队列,而LinkedBlockingDeque则是一个由链表组成的双向阻塞队列,双向队列就意味着可以从对头、对尾两端插入和移除元素,同样意味着LinkedBlockingDeque支持FIFO、FILO两种操作方式。LinkedBlockingDeque是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为Integer.MAX_VALUE。

成员变量

LinkedBlockingDeque 继承AbstractQueue,实现接口BlockingDeque,而BlockingDeque又继承接口BlockingQueue,BlockingDeque是支持两个附加操作的 Queue,这两个操作是:获取元素时等待双端队列变为非空;存储元素时等待双端队列中的空间变得可用。这两类操作就为LinkedBlockingDeque 的双向操作Queue提供了可能。BlockingDeque接口提供了一系列的以First和Last结尾的方法,如addFirst、addLast、peekFirst、peekLast。

public class LinkedBlockingDeque<E>
    extends AbstractQueue<E>
    implements BlockingDeque<E>, java.io.Serializable {

    // 双向链表的表头
    transient Node<E> first;

    // 双向链表的表尾
    transient Node<E> last;

    // 大小,双向链表中当前节点个数
    private transient int count;

    // 容量,在创建LinkedBlockingDeque时指定的
    private final int capacity;

    final ReentrantLock lock = new ReentrantLock();

    private final Condition notEmpty = lock.newCondition();

    private final Condition notFull = lock.newCondition();

}

通过上面的Lock可以看出,LinkedBlockingDeque底层实现机制与LinkedBlockingQueue一样,依然是通过互斥锁ReentrantLock 来实现,notEmpty 、notFull 两个Condition做协调生产者、消费者问题。

与其他BlockingQueue一样,节点还是使用内部类Node:

    static final class Node<E> {
        E item;

        Node<E> prev;

        Node<E> next;

        Node(E x) {
            item = x;
        }
    }

双向嘛,节点肯定得要有前驱prev、后继next咯。

基础方法

LinkedBlockingDeque 的add、put、offer、take、peek、poll系列方法都是通过调用XXXFirst,XXXLast方法。所以这里就仅以putFirst、putLast、pollFirst、pollLast分析下。

putFirst

putFirst(E e) :将指定的元素插入此双端队列的开头,必要时将一直等待可用空间。

    public void putFirst(E e) throws InterruptedException {
        // check null
        if (e == null) throw new NullPointerException();
        Node<E> node = new Node<E>(e);
        // 获取锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            while (!linkFirst(node))
                // 在notFull条件上等待,直到被唤醒或中断
                notFull.await();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

先获取锁,然后调用linkFirst方法入列,最后释放锁。如果队列是满的则在notFull上面等待。linkFirst设置Node为对头:

    private boolean linkFirst(Node<E> node) {
        // 超出容量
        if (count >= capacity)
            return false;

        // 首节点
        Node<E> f = first;
        // 新节点的next指向原first
        node.next = f;
        // 设置node为新的first
        first = node;

        // 没有尾节点,设置node为尾节点
        if (last == null)
            last = node;
        // 有尾节点,那就将之前first的pre指向新增node
        else
            f.prev = node;
        ++count;
        // 唤醒notEmpty
        notEmpty.signal();
        return true;
    }

linkFirst主要是设置node节点队列的列头节点,成功返回true,如果队列满了返回false。整个过程还是比较简单的。

putLast

putLast(E e) :将指定的元素插入此双端队列的末尾,必要时将一直等待可用空间。

    public void putLast(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        Node<E> node = new Node<E>(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            while (!linkLast(node))
                notFull.await();
        } finally {
            lock.unlock();
        }
    }

调用linkLast将节点Node链接到队列尾部:

    private boolean linkLast(Node<E> node) {
        if (count >= capacity)
            return false;
        // 尾节点
        Node<E> l = last;

        // 将Node的前驱指向原本的last
        node.prev = l;

        // 将node设置为last
        last = node;
        // 首节点为null,则设置node为first
        if (first == null)
            first = node;
        else
        //非null,说明之前的last有值,就将之前的last的next指向node
            l.next = node;
        ++count;
        notEmpty.signal();
        return true;
    }

pollFirst

pollFirst():获取并移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。

    public E pollFirst() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return unlinkFirst();
        } finally {
            lock.unlock();
        }
    }

调用unlinkFirst移除队列首元素:

   private E unlinkFirst() {
        // 首节点
        Node<E> f = first;

        // 空队列,直接返回null
        if (f == null)
            return null;

        // first.next
        Node<E> n = f.next;

        // 节点item
        E item = f.item;

        // 移除掉first ==> first = first.next
        f.item = null;
        f.next = f; // help GC
        first = n;

        // 移除后为空队列,仅有一个节点
        if (n == null)
            last = null;
        else
        // n的pre原来指向之前的first,现在n变为first了,pre指向null
            n.prev = null;
        --count;
        notFull.signal();
        return item;
    }

pollLast

pollLast():获取并移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null。

    public E pollLast() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return unlinkLast();
        } finally {
            lock.unlock();
        }
    }

调用unlinkLast移除尾结点,链表空返回null :

   private E unlinkLast() {
        // assert lock.isHeldByCurrentThread();
        Node<E> l = last;
        if (l == null)
            return null;
        Node<E> p = l.prev;
        E item = l.item;
        l.item = null;
        l.prev = l; // help GC
        last = p;
        if (p == null)
            first = null;
        else
            p.next = null;
        --count;
        notFull.signal();
        return item;
    }

LinkedBlockingDeque大部分方法都是通过linkFirst、linkLast、unlinkFirst、unlinkLast这四个方法来实现的,因为是双向队列,所以他们都是针对first、last的操作,看懂这个整个LinkedBlockingDeque就不难了。

PriorityBlockingQueue

我们知道线程Thread可以调用setPriority(int newPriority)来设置优先级的,线程优先级高的线程先执行,优先级低的后执行。而前面介绍的ArrayBlockingQueue、LinkedBlockingQueue都是采用FIFO原则来确定线程执行的先后顺序,那么有没有一个队列可以支持优先级呢? PriorityBlockingQueue 。

PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序升序排序,当然我们也可以通过构造函数来指定Comparator来对元素进行排序。需要注意的是PriorityBlockingQueue不能保证同优先级元素的顺序。

二叉堆

由于PriorityBlockingQueue底层采用二叉堆来实现的,所以有必要先介绍下二叉堆。

二叉堆是一种特殊的堆,就结构性而言就是完全二叉树或者是近似完全二叉树,满足树结构性和堆序性。树机构特性就是完全二叉树应该有的结构,堆序性则是:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子树和右子树都是一个二叉堆。它有两种表现形式:最大堆、最小堆。

最大堆:父节点的键值总是大于或等于任何一个子节点的键值(下右图)。

最小堆:父节点的键值总是小于或等于任何一个子节点的键值(下走图)。
在这里插入图片描述
二叉堆一般用数组表示,如果父节点的节点位置在n处,那么其左孩子节点为:2 * n + 1 ,其右孩子节点为2 * (n + 1),其父节点为(n – 1) / 2 处。上左图的数组表现形式为:
在这里插入图片描述
二叉堆的基本结构了解了,下面来看看二叉堆的添加和删除节点。二叉堆的添加和删除相对于二叉树来说会简单很多。

添加元素

首先将要添加的元素N插添加到堆的末尾位置(在二叉堆中我们称之为空穴)。如果元素N放入空穴中而不破坏堆的序(其值大于跟父节点值(最大堆是小于父节点)),那么插入完成。否则,我们则将该元素N的节点与其父节点进行交换,然后与其新父节点进行比较直到它的父节点不在比它小(最大堆是大)或者到达根节点。

假如有如下一个二叉堆
在这里插入图片描述
这是一个最小堆,其父节点总是小于等于任一一个子节点。现在我们添加一个元素2。

第一步:在末尾添加一个元素2,如下:
在这里插入图片描述
第二步:元素2比其父节点6小,进行替换,如下:
在这里插入图片描述
第三步:继续与其父节点5比较,小于,替换:
在这里插入图片描述
第四步:继续比较其跟节点1,发现跟节点比自己小,则完成,到这里元素2插入完毕。所以整个添加元素过程可以概括为:在元素末尾插入元素,然后不断比较替换直到不能移动为止。

复杂度:Ο(logn)

删除元素

删除元素与增加元素一样,需要维护整个二叉堆的序。删除位置1的元素(数组下标0),则把最后一个元素空出来移到最前边,然后和它的两个子节点比较,如果两个子节点中较小的节点小于该节点,就将他们交换,知道两个子节点都比该元素大为止。

就上面二叉堆而言,删除的元素为元素1。

第一步:删掉元素1,元素6空出来,如下:
在这里插入图片描述
第二步:与其两个子节点(元素2、元素3)比较,都小,将其中较小的元素(元素2)放入到该空穴中:
在这里插入图片描述
第三步:继续比较两个子节点(元素5、元素7),还是都小,则将较小的元素(元素5)放入到该空穴中:
在这里插入图片描述
第四步:比较其子节点(元素8),比该节点小,则元素6放入该空穴位置不会影响二叉堆的树结构,放入:
在这里插入图片描述
到这里整个删除操作就已经完成了。

二叉堆的添加、删除操作还是比较简单的,很容易就理解了。下面我们就参考该内容来开启PriorityBlockingQueue的源代码研究。

成员变量

PriorityBlockingQueue继承AbstractQueue,实现BlockingQueue接口。

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable

定义了一些属性:

    // 默认容量
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    // 最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    // 二叉堆数组
    private transient Object[] queue;

    // 队列元素的个数
    private transient int size;

    // 比较器,如果为空,则为自然顺序
    private transient Comparator<? super E> comparator;

    // 内部锁
    private final ReentrantLock lock;

    private final Condition notEmpty;

    //
    private transient volatile int allocationSpinLock;

    // 优先队列:主要用于序列化,这是为了兼容之前的版本。只有在序列化和反序列化才非空
    private PriorityQueue<E> q;

内部仍然采用可重入锁ReentrantLock来实现同步机制,但是这里只有一个notEmpty的Condition,了解了ArrayBlockingQueue我们知道它定义了两个Condition,之类为何只有一个呢?原因就在于PriorityBlockingQueue是一个无界队列,插入总是会成功,除非消耗尽了资源导致服务器挂。

入列

PriorityBlockingQueue提供put()、add()、offer()方法向队列中加入元素。我们这里从put()入手:put(E e) :将指定元素插入此优先级队列。

    public void put(E e) {
        offer(e); // never need to block
    }

PriorityBlockingQueue是无界的,所以不可能会阻塞。内部调用offer(E e):

    public boolean offer(E e) {
        // 不能为null
        if (e == null)
            throw new NullPointerException();
        // 获取锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        int n, cap;
        Object[] array;
        // 扩容
        while ((n = size) >= (cap = (array = queue).length))
            tryGrow(array, cap);
        try {
            Comparator<? super E> cmp = comparator;
            // 根据比较器是否为null,做不同的处理
            if (cmp == null)
                siftUpComparable(n, e, array);
            else
                siftUpUsingComparator(n, e, array, cmp);
            size = n + 1;
            // 唤醒正在等待的消费者线程
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
        return true;
    }

siftUpComparable

当比较器不为null时,采用所指定的比较器,调用siftUpUsingComparator方法:

    private static <T> void siftUpUsingComparator(int k, T x, Object[] array,
                                       Comparator<? super T> cmp) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = array[parent];
            if (cmp.compare(x, (T) e) >= 0)
                break;
            array[k] = e;
            k = parent;
        }
        array[k] = x;
    }

扩容:tryGrow

    private void tryGrow(Object[] array, int oldCap) {
        lock.unlock();      // 扩容操作使用自旋,不需要锁主锁,释放
        Object[] newArray = null;
        // CAS 占用
        if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) {
            try {

                // 新容量  最小翻倍
                int newCap = oldCap + ((oldCap < 64) ? (oldCap + 2) :  (oldCap >> 1));

                // 超过
                if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                    int minCap = oldCap + 1;
                    if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                        throw new OutOfMemoryError();
                    newCap = MAX_ARRAY_SIZE;        // 最大容量
                }
                if (newCap > oldCap && queue == array)
                    newArray = new Object[newCap];
            } finally {
                allocationSpinLock = 0;     // 扩容后allocationSpinLock = 0 代表释放了自旋锁
            }
        }
        // 到这里如果是本线程扩容newArray肯定是不为null,为null就是其他线程在处理扩容,那就让给别的线程处理
        if (newArray == null)
            Thread.yield();
        // 主锁获取锁
        lock.lock();
        // 数组复制
        if (newArray != null && queue == array) {
            queue = newArray;
            System.arraycopy(array, 0, newArray, 0, oldCap);
        }
    }

整个添加元素的过程和上面二叉堆一模一样:先将元素添加到数组末尾,然后采用“上冒”的方式将该元素尽量往上冒。

出列

PriorityBlockingQueue提供poll()、remove()方法来执行出对操作。出对的永远都是第一个元素:array[0]。

   public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

先获取锁,然后调用dequeue()方法:

    private E dequeue() {
        // 没有元素 返回null
        int n = size - 1;
        if (n < 0)
            return null;
        else {
            Object[] array = queue;
            // 出对元素
            E result = (E) array[0];
            // 最后一个元素(也就是插入到空穴中的元素)
            E x = (E) array[n];
            array[n] = null;
            // 根据比较器释放为null,来执行不同的处理
            Comparator<? super E> cmp = comparator;
            if (cmp == null)
                siftDownComparable(0, x, array, n);
            else
                siftDownUsingComparator(0, x, array, n, cmp);
            size = n;
            return result;
        }
    }

siftDownComparable

如果比较器为null,则调用siftDownComparable来进行自然排序处理:

    private static <T> void siftDownComparable(int k, T x, Object[] array,
                                               int n) {
        if (n > 0) {
            Comparable<? super T> key = (Comparable<? super T>)x;
            // 最后一个叶子节点的父节点位置
            int half = n >>> 1;
            while (k < half) {
                int child = (k << 1) + 1;       // 待调整位置左节点位置
                Object c = array[child];        //左节点
                int right = child + 1;          //右节点

                //左右节点比较,取较小的
                if (right < n &&
                        ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                    c = array[child = right];

                //如果待调整key最小,那就退出,直接赋值
                if (key.compareTo((T) c) <= 0)
                    break;
                //如果key不是最小,那就取左右节点小的那个放到调整位置,然后小的那个节点位置开始再继续调整
                array[k] = c;
                k = child;
            }
            array[k] = key;
        }
    }

处理思路和二叉堆删除节点的逻辑一样:就第一个元素定义为空穴,然后把最后一个元素取出来,尝试插入到空穴位置,并与两个子节点值进行比较,如果不符合,则与其中较小的子节点进行替换,然后继续比较调整。

siftDownUsingComparator

如果指定了比较器,则采用比较器来进行调整:

    private static <T> void siftDownUsingComparator(int k, T x, Object[] array,
                                                    int n,
                                                    Comparator<? super T> cmp) {
        if (n > 0) {
            int half = n >>> 1;
            while (k < half) {
                int child = (k << 1) + 1;
                Object c = array[child];
                int right = child + 1;
                if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
                    c = array[child = right];
                if (cmp.compare(x, (T) c) <= 0)
                    break;
                array[k] = c;
                k = child;
            }
            array[k] = x;
        }
    }

PriorityBlockingQueue采用二叉堆来维护,所以整个处理过程不是很复杂,添加操作则是不断“上冒”,而删除操作则是不断“下掉”。掌握二叉堆就掌握了PriorityBlockingQueue,无论怎么变还是。对于PriorityBlockingQueue需要注意的是他是一个无界队列,所以添加操作是不会失败的,除非资源耗尽。

DelayQueue

DelayQueue是一个支持延时获取元素的无界阻塞队列。里面的元素全部都是“可延期”的元素,列头的元素是最先“到期”的元素,如果队列里面没有元素到期,是不能从列头获取元素的,哪怕有元素也不行。也就是说只有在延迟期到时才能够从队列中取元素。

DelayQueue主要用于两个方面:

  • 缓存:清掉缓存中超时的缓存数据。
  • 任务超时处理。
成员变量

DelayQueue实现的关键主要有如下几个:

  1. 可重入锁ReentrantLock
  2. 用于阻塞和通知的Condition对象
  3. 根据Delay时间排序的优先级队列:PriorityQueue
  4. 用于优化阻塞通知的线程元素leader

Delayed

Delayed接口是用来标记那些应该在给定延迟时间之后执行的对象,它定义了一个long getDelay(TimeUnit unit)方法,该方法返回与此对象相关的的剩余时间。同时实现该接口的对象必须定义一个compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序。

public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}

如何使用该接口呢?上面说的非常清楚了,实现该接口的getDelay()方法,同时定义compareTo()方法即可。

先看DelayQueue的定义:

    public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
            implements BlockingQueue<E> {
        /** 可重入锁 */
        private final transient ReentrantLock lock = new ReentrantLock();
        /** 支持优先级的BlockingQueue */
        private final PriorityQueue<E> q = new PriorityQueue<E>();
        /** 用于优化阻塞 */
        private Thread leader = null;
        /** Condition */
        private final Condition available = lock.newCondition();

        /**
         * 省略很多代码
         */

看了DelayQueue的内部结构就对上面几个关键点一目了然了,但是这里有一点需要注意,DelayQueue的元素都必须继承Delayed接口。同时也可以从这里初步理清楚DelayQueue内部实现的机制了:以支持优先级无界队列的PriorityQueue作为一个容器,容器里面的元素都应该实现Delayed接口,在每次往优先级队列中添加元素时以元素的过期时间作为排序条件,最先过期的元素放在优先级最高。

offer()
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 向 PriorityQueue中插入元素
            q.offer(e);
            // 如果当前元素的对首元素(优先级最高),leader设置为空,唤醒所有等待线程
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            // 无界队列,永远返回true
            return true;
        } finally {
            lock.unlock();
        }
    }

整个过程还是比较简单,但是在判断当前元素是否为对首元素,如果是的话则设置leader=null,这是非常关键的一个步骤,后面阐述。

take()
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                // 对首元素
                E first = q.peek();
                // 对首为空,阻塞,等待off()操作唤醒
                if (first == null)
                    available.await();
                else {
                    // 获取对首元素的超时时间
                    long delay = first.getDelay(NANOSECONDS);
                    // <=0 表示已过期,出对,return
                    if (delay <= 0)
                        return q.poll();
                    first = null; // don't retain ref while waiting
                    // leader != null 证明有其他线程在操作,阻塞
                    if (leader != null)
                        available.await();
                    else {
                        // 否则将leader 设置为当前线程,独占
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            // 超时阻塞
                            available.awaitNanos(delay);
                        } finally {
                            // 释放leader
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            // 唤醒阻塞线程
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

首先是获取对首元素,如果对首元素的延时时间 delay <= 0 ,则可以出对了,直接return即可。否则设置first = null,这里设置为null的主要目的是为了避免内存泄漏。如果 leader != null 则表示当前有线程占用,则阻塞,否则设置leader为当前线程,然后调用awaitNanos()方法超时等待。

first = null

这里为什么如果不设置first = null,则会引起内存泄漏呢?线程A到达,列首元素没有到期,设置leader = 线程A,这是线程B来了因为leader != null,则会阻塞,线程C一样。假如线程阻塞完毕了,获取列首元素成功,出列。这个时候列首元素应该会被回收掉,但是问题是它还被线程B、线程C持有着,所以不会回收,这里只有两个线程,如果有线程D、线程E…呢?这样会无限期的不能回收,就会造成内存泄漏。

这个入队、出对过程和其他的阻塞队列没有很大区别,无非是在出对的时候增加了一个到期时间的判断。同时通过leader来减少不必要阻塞。

SynchronousQueue

作为BlockingQueue中的一员,SynchronousQueue与其他BlockingQueue有着不同特性:

  1. SynchronousQueue没有容量。与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
  2. 因为没有容量,所以对应 peek, contains, clear, isEmpty … 等方法其实是无效的。例如clear是不执行任何操作的,contains始终返回false,peek始终返回null。
  3. SynchronousQueue分为公平和非公平,默认情况下采用非公平性访问策略,当然也可以通过构造函数来设置为公平性访问策略(为true即可)。
  4. 若使用 TransferQueue, 则队列中永远会存在一个 dummy node(这点后面详细阐述)。

SynchronousQueue非常适合做交换工作,生产者的线程和消费者的线程同步以传递某些信息、事件或者任务。

成员变量

与其他BlockingQueue一样,SynchronousQueue同样继承AbstractQueue和实现BlockingQueue接口:

public class SynchronousQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable

SynchronousQueue提供了两个构造函数:

    public SynchronousQueue() {
        this(false);
    }

    public SynchronousQueue(boolean fair) {
        // 通过 fair 值来决定公平性和非公平性
        // 公平性使用TransferQueue,非公平性采用TransferStack
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }

TransferQueue、TransferStack继承Transferer,Transferer为SynchronousQueue的内部类,它提供了一个方法transfer(),该方法定义了转移数据的规范,如下:

    abstract static class Transferer<E> {
        abstract E transfer(E e, boolean timed, long nanos);
    }

transfer()方法主要用来完成转移数据的,如果e != null,相当于将一个数据交给消费者,如果e == null,则相当于从一个生产者接收一个消费者交出的数据。

SynchronousQueue采用队列TransferQueue来实现公平性策略,采用堆栈TransferStack来实现非公平性策略,他们两种都是通过链表实现的,其节点分别为QNode,SNode。TransferQueue和TransferStack在SynchronousQueue中扮演着非常重要的作用,SynchronousQueue的put、take操作都是委托这两个类来实现的。

TransferQueue

ransferQueue是实现公平性策略的核心类,其节点为QNode,其定义如下:

    static final class TransferQueue<E> extends Transferer<E> {
        /** 头节点 */
        transient volatile QNode head;
        /** 尾节点 */
        transient volatile QNode tail;
        // 指向一个取消的结点
        //当一个节点中最后一个插入时,它被取消了但是可能还没有离开队列
        transient volatile QNode cleanMe;

        /**
         * 省略很多代码O(∩_∩)O
         */
    }

在TransferQueue中除了头、尾节点外还存在一个cleanMe节点。该节点主要用于标记,当删除的节点是尾节点时则需要使用该节点。

同时,对于TransferQueue需要注意的是,其队列永远都存在一个dummy node,在构造时创建:

        TransferQueue() {
            QNode h = new QNode(null, false); // initialize to dummy node.
            head = h;
            tail = h;
        }

在TransferQueue中定义了QNode类来表示队列中的节点,QNode节点定义如下:

    static final class QNode {
        // next 域
        volatile QNode next;
        // item数据项
        volatile Object item;
        //  等待线程,用于park/unpark
        volatile Thread waiter;       // to control park/unpark
        //模式,表示当前是数据还是请求,只有当匹配的模式相匹配时才会交换
        final boolean isData;

        QNode(Object item, boolean isData) {
            this.item = item;
            this.isData = isData;
        }

        /**
         * CAS next域,在TransferQueue中用于向next推进
         */
        boolean casNext(QNode cmp, QNode val) {
            return next == cmp &&
                    UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

        /**
         * CAS itme数据项
         */
        boolean casItem(Object cmp, Object val) {
            return item == cmp &&
                    UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }

        /**
         *  取消本结点,将item域设置为自身
         */
        void tryCancel(Object cmp) {
            UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
        }

        /**
         * 是否被取消
         * 与tryCancel相照应只需要判断item释放等于自身即可
         */
        boolean isCancelled() {
            return item == this;
        }


        boolean isOffList() {
            return next == this;
        }

        private static final sun.misc.Unsafe UNSAFE;
        private static final long itemOffset;
        private static final long nextOffset;

        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> k = QNode.class;
                itemOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("item"));
                nextOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("next"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

上面代码没啥好看的,需要注意的一点就是isData,该属性在进行数据交换起到关键性作用,两个线程进行数据交换的时候,必须要两者的模式保持一致。

TransferStack

TransferStack用于实现非公平性,定义如下:

    static final class TransferStack<E> extends Transferer<E> {

        static final int REQUEST    = 0;

        static final int DATA       = 1;

        static final int FULFILLING = 2;

        volatile SNode head;

        /**
         * 省略一堆代码  O(∩_∩)O~
         */

    }

TransferStack中定义了三个状态:REQUEST表示消费数据的消费者,DATA表示生产数据的生产者,FULFILLING,表示匹配另一个生产者或消费者。任何线程对TransferStack的操作都属于上述3种状态中的一种(对应着SNode节点的mode)。同时还包含一个head域,表示头结点。

内部节点SNode定义如下:

    static final class SNode {
        // next 域
        volatile SNode next;
        // 相匹配的节点
        volatile SNode match;
        // 等待的线程
        volatile Thread waiter;
        // item 域
        Object item;                // data; or null for REQUESTs

        // 模型
        int mode;

        /**
         * item域和mode域不需要使用volatile修饰,因为它们在volatile/atomic操作之前写,之后读
         */

        SNode(Object item) {
            this.item = item;
        }

        boolean casNext(SNode cmp, SNode val) {
            return cmp == next &&
                    UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

        /**
         * 将s结点与本结点进行匹配,匹配成功,则unpark等待线程
         */
        boolean tryMatch(SNode s) {
            if (match == null &&
                    UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
                Thread w = waiter;
                if (w != null) {    // waiters need at most one unpark
                    waiter = null;
                    LockSupport.unpark(w);
                }
                return true;
            }
            return match == s;
        }

        void tryCancel() {
            UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
        }

        boolean isCancelled() {
            return match == this;
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long matchOffset;
        private static final long nextOffset;

        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> k = SNode.class;
                matchOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("match"));
                nextOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("next"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

上面简单介绍了TransferQueue、TransferStack,由于SynchronousQueue的put、take操作都是调用Transfer的transfer()方法,只不过是传递的参数不同而已,put传递的是e参数,所以模式为数据(公平isData = true,非公平mode= DATA),而take操作传递的是null,所以模式为请求(公平isData = false,非公平mode = REQUEST),如下:

    // put操作
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        if (transferer.transfer(e, false, 0) == null) {
            Thread.interrupted();
            throw new InterruptedException();
        }
    }

    // take操作
    public E take() throws InterruptedException {
        E e = transferer.transfer(null, false, 0);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }
公平模式

公平性调用TransferQueue的transfer方法:

    E transfer(E e, boolean timed, long nanos) {
        QNode s = null;
        // 当前节点模式
        boolean isData = (e != null);

        for (;;) {
            QNode t = tail;
            QNode h = head;
            // 头、尾节点 为null,没有初始化
            if (t == null || h == null)
                continue;

            // 头尾节点相等(队列为null) 或者当前节点和队列节点模式一样
            if (h == t || t.isData == isData) {
                // tn = t.next
                QNode tn = t.next;
                // t != tail表示已有其他线程操作了,修改了tail,重新再来
                if (t != tail)
                    continue;
                // tn != null,表示已经有其他线程添加了节点,tn 推进,重新处理
                if (tn != null) {
                    // 当前线程帮忙推进尾节点,就是尝试将tn设置为尾节点
                    advanceTail(t, tn);
                    continue;
                }
                //  调用的方法的 wait 类型的, 并且 超时了, 直接返回 null
                // timed 在take操作阐述
                if (timed && nanos <= 0)
                    return null;

                // s == null,构建一个新节点Node
                if (s == null)
                    s = new QNode(e, isData);

                // 将新建的节点加入到队列中,如果不成功,继续处理
                if (!t.casNext(null, s))
                    continue;

                // 替换尾节点
                advanceTail(t, s);

                // 调用awaitFulfill, 若节点是 head.next, 则进行自旋
                // 若不是的话, 直接 block, 直到有其他线程 与之匹配, 或它自己进行线程的中断
                Object x = awaitFulfill(s, e, timed, nanos);

                // 若返回的x == s表示,当前线程已经超时或者中断,不然的话s == null或者是匹配的节点
                if (x == s) {
                    // 清理节点S
                    clean(t, s);
                    return null;
                }

                // isOffList:用于判断节点是否已经从队列中离开了
                if (!s.isOffList()) {
                    // 尝试将S节点设置为head,移出t
                    advanceHead(t, s);
                    if (x != null)
                        s.item = s;
                    // 释放线程 ref
                    s.waiter = null;
                }

                // 返回
                return (x != null) ? (E)x : e;

            }

            // 这里是从head.next开始,因为TransferQueue总是会存在一个dummy node节点
            else {
                // 节点
                QNode m = h.next;

                // 不一致读,重新开始
                // 有其他线程更改了线程结构
                if (t != tail || m == null || h != head)
                    continue;

                /**
                 * 生产者producer和消费者consumer匹配操作
                 */
                Object x = m.item;
                // isData == (x != null):判断isData与x的模式是否相同,相同表示已经匹配了
                // x == m :m节点被取消了
                // !m.casItem(x, e):如果尝试将数据e设置到m上失败
                if (isData == (x != null) ||  x  == m || !m.casItem(x, e)) {
                    // 将m设置为头结点,h出列,然后重试
                    advanceHead(h, m);
                    continue;
                }

                // 成功匹配了,m设置为头结点h出列,向前推进
                advanceHead(h, m);
                // 唤醒m上的等待线程
                LockSupport.unpark(m.waiter);
                return (x != null) ? (E)x : e;
            }
        }
    }

整个transfer的算法如下:

  1. 如果队列为null或者尾节点模式与当前节点模式一致,则尝试将节点加入到等待队列中(采用自旋的方式),直到被匹配或、超时或者取消。匹配成功的话要么返回null(producer返回的)要么返回真正传递的值(consumer返回的),如果返回的是node节点本身则表示当前线程超时或者取消了。
  2. 如果队列不为null,且队列的节点是当前节点匹配的节点,则进行数据的传递匹配并返回匹配节点的数据
  3. 在整个过程中都会检测并帮助其他线程推进

当队列为空时,节点入列然后通过调用awaitFulfill()方法自旋,该方法主要用于自旋/阻塞节点,直到节点被匹配返回或者取消、中断。

    Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {

        // 超时控制
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        Thread w = Thread.currentThread();
        // 自旋次数
        // 如果节点Node恰好是head节点,则自旋一段时间,这里主要是为了效率问题,如果里面阻塞,会存在唤醒、线程上下文切换的问题
        // 如果生产者、消费者者里面到来的话,就避免了这个阻塞的过程
        int spins = ((head.next == s) ?
                (timed ? maxTimedSpins : maxUntimedSpins) : 0);
        // 自旋
        for (;;) {
            // 线程中断了,剔除当前节点
            if (w.isInterrupted())
                s.tryCancel(e);

            // 如果线程进行了阻塞 -> 唤醒或者中断了,那么x != e 肯定成立,直接返回当前节点即可
            Object x = s.item;
            if (x != e)
                return x;
            // 超时判断
            if (timed) {
                nanos = deadline - System.nanoTime();
                // 如果超时了,取消节点,continue,在if(x != e)肯定会成立,直接返回x
                if (nanos <= 0L) {
                    s.tryCancel(e);
                    continue;
                }
            }

            // 自旋- 1
            if (spins > 0)
                --spins;

            // 等待线程
            else if (s.waiter == null)
                s.waiter = w;

            // 进行没有超时的 park
            else if (!timed)
                LockSupport.park(this);

            // 自旋次数过了, 直接 + timeout 方式 park
            else if (nanos > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanos);
        }
    }

在自旋/阻塞过程中做了一点优化,就是判断当前节点是否为对头元素,如果是的则先自旋,如果自旋次数过了,则才阻塞,这样做的主要目的就在如果生产者、消费者立马来匹配了则不需要阻塞,因为阻塞、唤醒会消耗资源。在整个自旋的过程中会不断判断是否超时或者中断了,如果中断或者超时了则调用tryCancel()取消该节点。

tryCancel

		void tryCancel(Object cmp) {
                UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
        }

取消过程就是将节点的item设置为自身(itemOffset是item的偏移量)。所以在调用awaitFulfill()方法时,如果当前线程被取消、中断、超时了那么返回的值肯定时S,否则返回的则是匹配的节点。如果返回值是节点S,那么if(x == s)必定成立,如下:

		Object x = awaitFulfill(s, e, timed, nanos);
        if (x == s) {                   // wait was cancelled
           clean(t, s);
           return null;
        }

如果返回的x == s成立,则调用clean()方法清理节点S:

    void clean(QNode pred, QNode s) {
        //
        s.waiter = null;

        while (pred.next == s) {
            QNode h = head;
            QNode hn = h.next;
            // hn节点被取消了,向前推进
            if (hn != null && hn.isCancelled()) {
                advanceHead(h, hn);
                continue;
            }

            // 队列为空,直接return null
            QNode t = tail;
            if (t == h)
                return;

            QNode tn = t.next;
            // 不一致,说明有其他线程改变了tail节点,重新开始
            if (t != tail)
                continue;

            // tn != null 推进tail节点,重新开始
            if (tn != null) {
                advanceTail(t, tn);
                continue;
            }

            // s 不是尾节点 移出
            if (s != t) {
                QNode sn = s.next;
                // 如果s已经被移除退出循环,否则尝试断开s
                if (sn == s || pred.casNext(s, sn))
                    return;
            }

            // s是尾节点,则有可能会有其他线程在添加新节点,则cleanMe出场
            QNode dp = cleanMe;
            // 如果dp不为null,说明是前一个被取消节点,将其移除
            if (dp != null) {
                QNode d = dp.next;
                QNode dn;
                if (d == null ||               // 节点d已经删除
                        d == dp ||                 // 原来的节点 cleanMe 已经通过 advanceHead 进行删除
                        !d.isCancelled() ||        // 原来的节点 s已经删除
                        (d != t &&                 // d 不是tail节点
                                (dn = d.next) != null &&  //
                                dn != d &&                //   that is on list
                                dp.casNext(d, dn)))       // d unspliced
                    // 清除 cleanMe 节点, 这里的 dp == pred 若成立, 说明清除节点s,成功, 直接return, 不然的话要再次循环
                    casCleanMe(dp, null);
                if (dp == pred)
                    return;
            } else if (casCleanMe(null, pred))  // 原来的 cleanMe 是 null, 则将 pred 标记为 cleamMe 为下次 清除 s 节点做标识
                return;
        }
    }

这个clean()方法感觉有点儿难度,我也看得不是很懂。

  1. 删除的节点不是queue尾节点, 这时 直接 pred.casNext(s, s.next) 方式来进行删除(和ConcurrentLikedQueue中差不多)。
  2. 删除的节点是队尾节点:1)此时 cleanMe == null, 则 前继节点pred标记为 cleanMe, 为下次删除做准备 2)此时 cleanMe != null, 先删除上次需要删除的节点, 然后将 cleanMe至null, 让后再将 pred 赋值给 cleanMe
非公平模式

非公平模式transfer方法如下:

   E transfer(E e, boolean timed, long nanos) {
        SNode s = null; // constructed/reused as needed
        int mode = (e == null) ? REQUEST : DATA;

        for (;;) {
            SNode h = head;
            // 栈为空或者当前节点模式与头节点模式一样,将节点压入栈内,等待匹配
            if (h == null || h.mode == mode) {
                // 超时
                if (timed && nanos <= 0) {
                    // 节点被取消了,向前推进
                    if (h != null && h.isCancelled())
                        //  重新设置头结点(弹出之前的头结点)
                        casHead(h, h.next);
                    else
                        return null;
                }
                // 不超时
                // 生成一个SNode节点,并尝试替换掉头节点head (head -> s)
                else if (casHead(h, s = snode(s, e, h, mode))) {
                    // 自旋,等待线程匹配
                    SNode m = awaitFulfill(s, timed, nanos);
                    // 返回的m == s 表示该节点被取消了或者超时、中断了
                    if (m == s) {
                        // 清理节点S,return null
                        clean(s);
                        return null;
                    }

                    // 因为通过前面一步将S替换成了head,如果h.next == s ,则表示有其他节点插入到S前面了,变成了head
                    // 且该节点就是与节点S匹配的节点
                    if ((h = head) != null && h.next == s)
                        // 将s.next节点设置为head,相当于取消节点h、s
                        casHead(h, s.next);

                    // 如果是请求则返回匹配的域,否则返回节点S的域
                    return (E) ((mode == REQUEST) ? m.item : s.item);
                }
            }

            // 如果栈不为null,且两者模式不匹配(h != null && h.mode != mode)
            // 说明他们是一队对等匹配的节点,尝试用当前节点s来满足h节点
            else if (!isFulfilling(h.mode)) {
                // head 节点已经取消了,向前推进
                if (h.isCancelled())
                    casHead(h, h.next);

                // 尝试将当前节点打上"正在匹配"的标记,并设置为head
                else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                    // 循环loop
                    for (;;) {
                        // s为当前节点,m是s的next节点,
                        // m节点是s节点的匹配节点
                        SNode m = s.next;
                        // m == null,其他节点把m节点匹配走了
                        if (m == null) {
                            // 将s弹出
                            casHead(s, null);
                            // 将s置空,下轮循环的时候还会新建
                            s = null;
                            // 退出该循环,继续主循环
                            break;
                        }
                        // 获取m的next节点
                        SNode mn = m.next;
                        // 尝试匹配
                        if (m.tryMatch(s)) {
                            // 匹配成功,将s 、 m弹出
                            casHead(s, mn);     // pop both s and m
                            return (E) ((mode == REQUEST) ? m.item : s.item);
                        } else
                            // 如果没有匹配成功,说明有其他线程已经匹配了,把m移出
                            s.casNext(m, mn);
                    }
                }
            }
            // 到这最后一步说明节点正在匹配阶段
            else {
                // head 的next的节点,是正在匹配的节点,m 和 h配对
                SNode m = h.next;

                // m == null 其他线程把m节点抢走了,弹出h节点
                if (m == null)
                    casHead(h, null);
                else {
                    SNode mn = m.next;
                    if (m.tryMatch(h))
                        casHead(h, mn);
                    else
                        h.casNext(m, mn);
                }
            }
        }
    }

整个处理过程分为三种情况,具体如下:

  1. 如果当前栈为空获取节点模式与栈顶模式一样,则尝试将节点加入栈内,同时通过自旋方式等待节点匹配,最后返回匹配的节点或者null(被取消)
  2. 如果栈不为空且节点的模式与首节点模式匹配,则尝试将该节点打上FULFILLING标记,然后加入栈中,与相应的节点匹配,成功后将这两个节点弹出栈并返回匹配节点的数据
  3. 如果有节点在匹配,那么帮助这个节点完成匹配和出栈操作,然后在主循环中继续执行

当节点加入栈内后,通过调用awaitFulfill()方法自旋等待节点匹配:

    SNode awaitFulfill(SNode s, boolean timed, long nanos) {
        // 超时
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        // 当前线程
        Thread w = Thread.currentThread();

        // 自旋次数
        // shouldSpin 用于检测当前节点是否需要自旋
        // 如果栈为空、该节点是首节点或者该节点是匹配节点,则先采用自旋,否则阻塞
        int spins = (shouldSpin(s) ?
                (timed ? maxTimedSpins : maxUntimedSpins) : 0);
        for (;;) {
            // 线程中断了,取消该节点
            if (w.isInterrupted())
                s.tryCancel();

            // 匹配节点
            SNode m = s.match;

            // 如果匹配节点m不为空,则表示匹配成功,直接返回
            if (m != null)
                return m;
            // 超时
            if (timed) {
                nanos = deadline - System.nanoTime();
                // 节点超时,取消
                if (nanos <= 0L) {
                    s.tryCancel();
                    continue;
                }
            }

            // 自旋;每次自旋的时候都需要检查自身是否满足自旋条件,满足就 - 1,否则为0
            if (spins > 0)
                spins = shouldSpin(s) ? (spins-1) : 0;

            // 第一次阻塞时,会将当前线程设置到s上
            else if (s.waiter == null)
                s.waiter = w;

            // 阻塞 当前线程
            else if (!timed)
                LockSupport.park(this);
            // 超时
            else if (nanos > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanos);
        }
    }

awaitFulfill()方法会一直自旋/阻塞直到匹配节点。在S节点阻塞之前会先调用shouldSpin()方法判断是否采用自旋方式,为的就是如果有生产者或者消费者马上到来,就不需要阻塞了,在多核条件下这种优化是有必要的。同时在调用park()阻塞之前会将当前线程设置到S节点的waiter上。匹配成功,返回匹配节点m。

shouldSpin()方法如下:

        boolean shouldSpin(SNode s) {
            SNode h = head;
            return (h == s || h == null || isFulfilling(h.mode));
        }

同时在阻塞过程中会一直检测当前线程是否中断了,如果中断了,则调用tryCancel()方法取消该节点,取消过程就是将当前节点的math设置为当前节点。所以如果线程中断了,那么在返回m时一定是S节点自身。

void tryCancel() {
     UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
}

awaitFullfill()方法如果返回的m == s,则表示当前节点已经中断取消了,则需要调用clean()方法,清理节点S:

    void clean(SNode s) {

        // 清理item域
        s.item = null;
        // 清理waiter域
        s.waiter = null;

        // past节点
        SNode past = s.next;
        if (past != null && past.isCancelled())
            past = past.next;

        // 从栈顶head节点,取消从栈顶head到past节点之间所有已经取消的节点
        // 注意:这里如果遇到一个节点没有取消,则会退出while
        SNode p;
        while ((p = head) != null && p != past && p.isCancelled())
            casHead(p, p.next);     // 如果p节点已经取消了,则剔除该节点

        // 如果经历上面while p节点还没有取消,则再次循环取消掉所有p 到past之间的取消节点
        while (p != null && p != past) {
            SNode n = p.next;
            if (n != null && n.isCancelled())
                p.casNext(n, n.next);
            else
                p = n;
        }
    }

clean()方法就是将head节点到S节点之间所有已经取消的节点全部移出。

LinkedTransferQueue

前面提到的各种BlockingQueue对读或者写都是锁上整个队列,在并发量大的时候,各种锁是比较耗资源和耗时间的,而前面的SynchronousQueue虽然不会锁住整个队列,但它是一个没有容量的“队列”,那么有没有这样一种队列,它即可以像其他的BlockingQueue一样有容量又可以像SynchronousQueue一样不会锁住整个队列呢?有!答案就是LinkedTransferQueue。

LinkedTransferQueue是基于链表的FIFO无界阻塞队列,它出现在JDK7中。Doug Lea 大神说LinkedTransferQueue是一个聪明的队列。它是ConcurrentLinkedQueue、SynchronousQueue (公平模式下)、无界的LinkedBlockingQueues等的超集。既然这么牛逼,那势必要弄清楚其中的原理了。

成员变量

LinkedTransferQueue采用一种预占模式。什么意思呢?有就直接拿走,没有就占着这个位置直到拿到或者超时或者中断。即消费者线程到队列中取元素时,如果发现队列为空,则会生成一个null节点,然后park住等待生产者。后面如果生产者线程入队时发现有一个null元素节点,这时生产者就不会入列了,直接将元素填充到该节点上,唤醒该节点的线程,被唤醒的消费者线程拿东西走人。是不是有点儿SynchronousQueue的味道?

结构
LinkedTransferQueue与其他的BlockingQueue一样,同样继承AbstractQueue类,但是它实现了TransferQueue,TransferQueue接口继承BlockingQueue,所以TransferQueue算是对BlockingQueue一种扩充,该接口提供了一整套的transfer接口:

    public interface TransferQueue<E> extends BlockingQueue<E> {

        /**
         * 若当前存在一个正在等待获取的消费者线程(使用take()或者poll()函数),使用该方法会即刻转移/传输对象元素e;
         * 若不存在,则返回false,并且不进入队列。这是一个不阻塞的操作
         */
        boolean tryTransfer(E e);

        /**
         * 若当前存在一个正在等待获取的消费者线程,即立刻移交之;
         * 否则,会插入当前元素e到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素
         */
        void transfer(E e) throws InterruptedException;

        /**
         * 若当前存在一个正在等待获取的消费者线程,会立即传输给它;否则将插入元素e到队列尾部,并且等待被消费者线程获取消费掉;
         * 若在指定的时间内元素e无法被消费者线程获取,则返回false,同时该元素被移除。
         */
        boolean tryTransfer(E e, long timeout, TimeUnit unit)
                throws InterruptedException;

        /**
         * 判断是否存在消费者线程
         */
        boolean hasWaitingConsumer();

        /**
         * 获取所有等待获取元素的消费线程数量
         */
        int getWaitingConsumerCount();
    }

相对于其他的BlockingQueue,LinkedTransferQueue就多了上面几个方法。这几个方法在LinkedTransferQueue中起到了核心作用。

LinkedTransferQueue定义的变量如下:

    // 判断是否为多核
    private static final boolean MP =
            Runtime.getRuntime().availableProcessors() > 1;

    // 自旋次数
    private static final int FRONT_SPINS   = 1 << 7;

    // 前驱节点正在处理,当前节点需要自旋的次数
    private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;

    static final int SWEEP_THRESHOLD = 32;

    // 头节点
    transient volatile Node head;

    // 尾节点
    private transient volatile Node tail;

    // 删除节点失败的次数
    private transient volatile int sweepVotes;

    /*
     * 调用xfer()方法时需要传入,区分不同处理
     * xfer()方法是LinkedTransferQueue的最核心的方法
     */
    private static final int NOW   = 0; // for untimed poll, tryTransfer
    private static final int ASYNC = 1; // for offer, put, add
    private static final int SYNC  = 2; // for transfer, take
    private static final int TIMED = 3; // for timed poll, tryTransfer
Node节点

Node节点由四个部分构成:

  • isData:表示该节点是存放数据还是获取数据
  • item:存放数据,isData为false时,该节点为null,为true时,匹配后,该节点会置为null。
  • next:指向下一个节点。
  • waiter:park住消费者线程,线程就放在这里。

源码如下:

    static final class Node {
        // 表示该节点是存放数据还是获取数据
        final boolean isData;
        // 存放数据,isData为false时,该节点为null,为true时,匹配后,该节点会置为null
        volatile Object item;
        //指向下一个节点
        volatile Node next;

        // park住消费者线程,线程就放在这里
        volatile Thread waiter; // null until waiting

        /**
         * CAS Next域
         */
        final boolean casNext(Node cmp, Node val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

        /**
         * CAS itme域
         */
        final boolean casItem(Object cmp, Object val) {
            return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }

        /**
         * 构造函数
         */
        Node(Object item, boolean isData) {
            UNSAFE.putObject(this, itemOffset, item); // relaxed write
            this.isData = isData;
        }

        /**
         * 将next域指向自身,其实就是剔除节点
         */
        final void forgetNext() {
            UNSAFE.putObject(this, nextOffset, this);
        }

        /**
         *  匹配过或节点被取消的时候会调用
         */
        final void forgetContents() {
            UNSAFE.putObject(this, itemOffset, this);
            UNSAFE.putObject(this, waiterOffset, null);
        }

        /**
         * 校验节点是否匹配过,如果匹配做取消了,item则会发生变化
         */
        final boolean isMatched() {
            Object x = item;
            return (x == this) || ((x == null) == isData);
        }

        /**
         * 是否是一个未匹配的请求节点
         * 如果是的话isData应为false,item == null,因位如果匹配了,item则会有值
         */
        final boolean isUnmatchedRequest() {
            return !isData && item == null;
        }

        /**
         * 如给定节点类型不能挂在当前节点后返回true
         */
        final boolean cannotPrecede(boolean haveData) {
            boolean d = isData;
            Object x;
            return d != haveData && (x = item) != this && (x != null) == d;
        }

        /**
         * 匹配一个数据节点
         */
        final boolean tryMatchData() {
            // assert isData;
            Object x = item;
            if (x != null && x != this && casItem(x, null)) {
                LockSupport.unpark(waiter);
                return true;
            }
            return false;
        }

        private static final long serialVersionUID = -3375979862319811754L;

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long itemOffset;
        private static final long nextOffset;
        private static final long waiterOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> k = Node.class;
                itemOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("item"));
                nextOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("next"));
                waiterOffset = UNSAFE.objectFieldOffset
                        (k.getDeclaredField("waiter"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

节点Node为LinkedTransferQueue的内部类,其内部结构和公平方式的SynchronousQueue差不多,里面也同样提供了一些很重要的方法。

put操作

LinkedTransferQueue提供了add、put、offer三类方法,用于将元素插入队列中,如下:

    public void put(E e) {
        xfer(e, true, ASYNC, 0);
    }

    public boolean offer(E e, long timeout, TimeUnit unit) {
        xfer(e, true, ASYNC, 0);
        return true;
    }

    public boolean offer(E e) {
        xfer(e, true, ASYNC, 0);
        return true;
    }

    public boolean add(E e) {
        xfer(e, true, ASYNC, 0);
        return true;
    }

由于LinkedTransferQueue是无界的,不会阻塞,所以在调用xfer方法是传入的是ASYNC,同时直接返回true。

take操作

LinkedTransferQueue提供了poll、take方法用于出列元素:

    public E take() throws InterruptedException {
        E e = xfer(null, false, SYNC, 0);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }

    public E poll() {
        return xfer(null, false, NOW, 0);
    }

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E e = xfer(null, false, TIMED, unit.toNanos(timeout));
        if (e != null || !Thread.interrupted())
            return e;
        throw new InterruptedException();
    }

这里和put操作有点不一样,take()方法传入的是SYNC,阻塞。poll()传入的是NOW,poll(long timeout, TimeUnit unit)则是传入TIMED。

tranfer操作

实现TransferQueue接口,就要实现它的方法:

public boolean tryTransfer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    if (xfer(e, true, TIMED, unit.toNanos(timeout)) == null)
        return true;
    if (!Thread.interrupted())
        return false;
    throw new InterruptedException();
}

public void transfer(E e) throws InterruptedException {
    if (xfer(e, true, SYNC, 0) != null) {
        Thread.interrupted(); // failure possible only due to interrupt
        throw new InterruptedException();
    }
}

public boolean tryTransfer(E e) {
    return xfer(e, true, NOW, 0) == null;
}
xfer()

通过上面几个核心方法的源码我们清楚可以看到,最终都是调用xfer()方法,该方法接受四个参数,item或者null的E,put操作为true、take操作为false的havaData,how(有四个值NOW, ASYNC, SYNC, or TIMED,分别表示不同的操作),超时nanos。

    private E xfer(E e, boolean haveData, int how, long nanos) {

        // havaData为true,但是e == null 抛出空指针
        if (haveData && (e == null))
            throw new NullPointerException();
        Node s = null;                        // the node to append, if needed

        retry:
        for (;;) {

            // 从首节点开始匹配
            // p == null 队列为空
            for (Node h = head, p = h; p != null;) {

                // 模型,request or data
                boolean isData = p.isData;
                // item域
                Object item = p.item;

                // 找到一个没有匹配的节点
                // item != p 也就是自身,则表示没有匹配过
                // (item != null) == isData,表示模型符合
                if (item != p && (item != null) == isData) {

                    // 节点类型和待处理类型一致,这样肯定是不能匹配的
                    if (isData == haveData)   // can't match
                        break;
                    // 匹配,将E加入到item域中
                    // 如果p 的item为data,那么e为null,如果p的item为null,那么e为data
                    if (p.casItem(item, e)) { // match
                        //
                        for (Node q = p; q != h;) {
                            Node n = q.next;  // update by 2 unless singleton
                            if (head == h && casHead(h, n == null ? q : n)) {
                                h.forgetNext();
                                break;
                            }                 // advance and retry
                            if ((h = head)   == null ||
                                    (q = h.next) == null || !q.isMatched())
                                break;        // unless slack < 2
                        }

                        // 匹配后唤醒p的waiter线程;reservation则叫人收货,data则叫null收货
                        LockSupport.unpark(p.waiter);
                        return LinkedTransferQueue.<E>cast(item);
                    }
                }
                // 如果已经匹配了则向前推进
                Node n = p.next;
                // 如果p的next指向p本身,说明p节点已经有其他线程处理过了,只能从head重新开始
                p = (p != n) ? n : (h = head); // Use head if p offlist
            }

            // 如果没有找到匹配的节点,则进行处理
            // NOW为untimed poll, tryTransfer,不需要入队
            if (how != NOW) {                 // No matches available
                // s == null,新建一个节点
                if (s == null)
                    s = new Node(e, haveData);
                // 入队,返回前驱节点
                Node pred = tryAppend(s, haveData);
                // 返回的前驱节点为null,那就是有race,被其他的抢了,那就continue 整个for
                if (pred == null)
                    continue retry;

                // ASYNC不需要阻塞等待
                if (how != ASYNC)
                    return awaitMatch(s, pred, e, (how == TIMED), nanos);
            }
            return e;
        }
    }

整个算法的核心就是寻找匹配节点找到了就返回,否则就入队(NOW直接返回):

  • matched。判断匹配条件(isData不一样,本身没有匹配),匹配后就casItem,然后unpark匹配节点的waiter线程,如果是reservation则叫人收货,data则叫null收货。
  • unmatched。如果没有找到匹配节点,则根据传入的how来处理,NOW直接返回,其余三种先入对,入队后如果是ASYNC则返回,SYNC和TIMED则会阻塞等待匹配。

其实相当于SynchronousQueue来说,这个处理逻辑还是比较简单的。

如果没有找到匹配节点,且how != NOW会入队,入队则是调用tryAppend方法:

    private Node tryAppend(Node s, boolean haveData) {
        // 从尾节点tail开始
        for (Node t = tail, p = t;;) {
            Node n, u;

            // 队列为空则将节点S设置为head
            if (p == null && (p = head) == null) {
                if (casHead(null, s))
                    return s;
            }

            // 如果为data
            else if (p.cannotPrecede(haveData))
                return null;

            // 不是最后一个节点
            else if ((n = p.next) != null)
                p = p != t && t != (u = tail) ? (t = u) : (p != n) ? n : null;
            // CAS失败,一般来说失败的原因在于p.next != null,可能有其他增加了tail,向前推荐
            else if (!p.casNext(null, s))
                p = p.next;                   // re-read on CAS failure
            else {
                if (p != t) {                 // update if slack now >= 2
                    while ((tail != t || !casTail(t, s)) &&
                            (t = tail)   != null &&
                            (s = t.next) != null && // advance and retry
                            (s = s.next) != null && s != t);
                }
                return p;
            }
        }
    }

tryAppend方法是将S节点添加到tail上,然后返回其前驱节点。

加入队列后,如果how还不是ASYNC则调用awaitMatch()方法阻塞等待:

  private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
        // 超时控制
        final long deadline = timed ? System.nanoTime() + nanos : 0L;

        // 当前线程
        Thread w = Thread.currentThread();

        // 自旋次数
        int spins = -1; // initialized after first item and cancel checks

        // 随机数
        ThreadLocalRandom randomYields = null; // bound if needed

        for (;;) {
            Object item = s.item;
            //匹配了,可能有其他线程匹配了线程
            if (item != e) {
                // 撤销该节点
                s.forgetContents();
                return LinkedTransferQueue.<E>cast(item);
            }

            // 线程中断或者超时了。则调用将s节点item设置为e,等待取消
            if ((w.isInterrupted() || (timed && nanos <= 0)) && s.casItem(e, s)) {        // cancel
                // 断开节点
                unsplice(pred, s);
                return e;
            }

            // 自旋
            if (spins < 0) {
                // 计算自旋次数
                if ((spins = spinsFor(pred, s.isData)) > 0)
                    randomYields = ThreadLocalRandom.current();
            }

            // 自旋
            else if (spins > 0) {
                --spins;
                // 生成的随机数 == 0 ,停止线程?不是很明白....
                if (randomYields.nextInt(CHAINED_SPINS) == 0)
                    Thread.yield();
            }

            // 将当前线程设置到节点的waiter域
            // 一开始s.waiter == null 肯定是会成立的,
            else if (s.waiter == null) {
                s.waiter = w;                 // request unpark then recheck
            }

            // 超时阻塞
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos > 0L)
                    LockSupport.parkNanos(this, nanos);
            }
            else {
                // 不是超时阻塞
                LockSupport.park(this);
            }
        }
    }

整个awaitMatch过程和SynchronousQueue的awaitFulfill没有很大区别,不过在自旋过程会调用Thread.yield();

在awaitMatch过程中,如果线程中断了,或者超时了则会调用unsplice()方法去除该节点:

    final void unsplice(Node pred, Node s) {
        s.forgetContents(); // forget unneeded fields

        if (pred != null && pred != s && pred.next == s) {
            Node n = s.next;
            if (n == null ||
                    (n != s && pred.casNext(s, n) && pred.isMatched())) {

                for (;;) {               // check if at, or could be, head
                    Node h = head;
                    if (h == pred || h == s || h == null)
                        return;          // at head or list empty
                    if (!h.isMatched())
                        break;
                    Node hn = h.next;
                    if (hn == null)
                        return;          // now empty
                    if (hn != h && casHead(h, hn))
                        h.forgetNext();  // advance head
                }
                if (pred.next != pred && s.next != s) { // recheck if offlist
                    for (;;) {           // sweep now if enough votes
                        int v = sweepVotes;
                        if (v < SWEEP_THRESHOLD) {
                            if (casSweepVotes(v, v + 1))
                                break;
                        }
                        else if (casSweepVotes(v, 0)) {
                            sweep();
                            break;
                        }
                    }
                }
            }
        }
    }

主体流程已经完成,这里总结下:

  1. 无论是入对、出对,还是交换,最终都会跑到xfer(E e, boolean haveData, int how, long nanos)方法中,只不过传入的how不同而已
  2. 如果队列不为空,则尝试在队列中寻找是否存在与该节点相匹配的节点,如果找到则将匹配节点的item设置e,然后唤醒匹配节点的waiter线程。如果是reservation则叫人收货,data则叫null收货
  3. 如果队列为空,或者没有找到匹配的节点且how != NOW,则调用tryAppend()方法将节点添加到队列的tail,然后返回其前驱节点
  4. 如果节点的how != NOW && how != ASYNC,则调用awaitMatch()方法阻塞等待,在阻塞等待过程中和SynchronousQuque的awaitFulfill()逻辑差不多,都是先自旋,然后判断是否需要自旋,如果中断或者超时了则将该节点从队列中移出。
    在这里插入图片描述
BlockingQueue总结

BlockingQueue接口实现Queue接口,它支持两个附加操作:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。相对于同一操作他提供了四种机制:抛出异常、返回特殊值、阻塞等待、超时:
在这里插入图片描述
BlockingQueue常用于生产者和消费者场景。

JDK 8 中提供了七个阻塞队列可供使用(上图的DelayedWorkQueue是ScheduledThreadPoolExecutor的内部类):

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

基于数组的阻塞队列,ArrayBlockingQueue内部维护这一个定长数组,阻塞队列的大小在初始化时就已经确定了,其后无法更改。

采用可重入锁ReentrantLock来保证线程安全性,但是生产者和消费者是共用同一个锁对象,这样势必会导致降低一定的吞吐量。当然ArrayBlockingQueue完全可以采用分离锁来实现生产者和消费者的并行操作,但是我认为这样做只会给代码带来额外的复杂性,对于性能而言应该不会有太大的提升,因为基于数组的ArrayBlockingQueue在数据的写入和读取操作已经非常轻巧了。

ArrayBlockingQueue支持公平性和非公平性,默认采用非公平模式,可以通过构造函数设置为公平访问策略(true)。

PriorityBlockingQueue

PriorityBlockingQueue是支持优先级的无界队列。默认情况下采用自然顺序排序,当然也可以通过自定义Comparator来指定元素的排序顺序。

PriorityBlockingQueue内部采用二叉堆的实现方式,整个处理过程并不是特别复杂。添加操作则是不断“上冒”,而删除操作则是不断“下掉”。

DelayQueue

DelayQueue是一个支持延时操作的无界阻塞队列。列头的元素是最先“到期”的元素,如果队列里面没有元素到期,是不能从列头获取元素的,哪怕有元素也不行。也就是说只有在延迟期满时才能够从队列中去元素。

它主要运用于如下场景:

  • 缓存系统的设计:缓存是有一定的时效性的,可以用DelayQueue保存缓存的有效期,然后利用一个线程查询DelayQueue,如果取到元素就证明该缓存已经失效了。
  • 定时任务的调度:DelayQueue保存当天将要执行的任务和执行时间,一旦取到元素(任务),就执行该任务。

DelayQueue采用支持优先级的PriorityQueue来实现,但是队列中的元素必须要实现Delayed接口,Delayed接口用来标记那些应该在给定延迟时间之后执行的对象,该接口提供了getDelay()方法返回元素节点的剩余时间。同时,元素也必须要实现compareTo()方法,compareTo()方法需要提供与getDelay()方法一致的排序。

SynchronousQueue

SynchronousQueue是一个神奇的队列,他是一个不存储元素的阻塞队列,也就是说他的每一个put操作都需要等待一个take操作,否则就不能继续添加元素了,有点儿像Exchanger,类似于生产者和消费者进行交换。

队列本身不存储任何元素,所以非常适用于传递性场景,两者直接进行对接。其吞吐量会高于ArrayBlockingQueue和LinkedBlockingQueue。

SynchronousQueue支持公平和非公平的访问策略,在默认情况下采用非公平性,也可以通过构造函数来设置为公平性。

SynchronousQueue的实现核心为Transferer接口,该接口有TransferQueue和TransferStack两个实现类,分别对应着公平策略和非公平策略。接口Transferer有一个tranfer()方法,该方法定义了转移数据,如果e != null,相当于将一个数据交给消费者,如果e == null,则相当于从一个生产者接收一个消费者交出的数据。

LinkedTransferQueue

LinkedTransferQueue是一个由链表组成的的无界阻塞队列,该队列是一个相当牛逼的队列:它是ConcurrentLinkedQueue、SynchronousQueue (公平模式下)、无界的LinkedBlockingQueues等的超集。

与其他BlockingQueue相比,他多实现了一个接口TransferQueue,该接口是对BlockingQueue的一种补充,多了tryTranfer()和transfer()两类方法:

  • tranfer():若当前存在一个正在等待获取的消费者线程,即立刻移交之。 否则,会插入当前元素e到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素。
  • tryTranfer(): 若当前存在一个正在等待获取的消费者线程(使用take()或者poll()函数),使用该方法会即刻转移/传输对象元素e;若不存在,则返回false,并且不进入队列。这是一个不阻塞的操作。
LinkedBlockingDeque

LinkedBlockingDeque是一个有链表组成的双向阻塞队列,与前面的阻塞队列相比它支持从两端插入和移出元素。以first结尾的表示从对头操作,以last结尾的表示从对尾操作。

在初始化LinkedBlockingDeque时可以初始化队列的容量,用来防止其再扩容时过渡膨胀。另外双向阻塞队列可以运用在“工作窃取”模式中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值