JAVA并发:阻塞队列


在并发编程中,有时候需要使用线程安全的队列。如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁) 等方式来实现。非阻塞的实现方式则可以使用循环CAS的方式来实现。

下面分析的是阻塞队列。

1.什么是阻塞队列

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

  • 支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满。

  • 支持阻塞的移除方法:当队列为空时,获取元素的线程会等待队列变为非空。

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素,消费者则从队列里取元素。

2.队列Queue接口核心方法

先简单介绍下JAVA容器中的队列有哪些接口及使用方法:

image-20201124232843047

3.阻塞队列BlockigQueue接口核心方法

阻塞队列,本质上来说还是属于队列,也就是说阻塞队列继承了队列的功能。

image-20201124233130746

4.常用的阻塞队列

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列

  • LinkedBlockingQueue:由链表结构组成的有界阻塞队列

  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列

  • DelayQueue:使用优先级队列实现的无界阻塞队列

  • SynchronousQueue:不存储元素的阻塞队列

  • LinkedTransferQueue:由链表结构组成的无界阻塞队列

  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列

以下逐一介绍这7种阻塞队列

4.1ArrayBlockingQueue(有界阻塞数组队列)

ArrayBlockingQueue是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下采用非公平锁的方式实现,可以通过构造器传参控制是采用公平锁还是非公平锁实现。先看看ArrayBlockingQueue类图关系:

image-20201124234406473

4.1.1阻塞队列的使用

模拟生产者与消费者

private static void consumeDemo() throws InterruptedException {
    Thread consume = new Thread(() -> {
        while (true) {
            try {
                Integer a = consumeQueue.take();
                System.out.println("消费:" + a);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    Thread product = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            try {
                consumeQueue.put(i);
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });

    consume.start();
    TimeUnit.SECONDS.sleep(5);
    product.start();
}

4.1.2初始化队列

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

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    // 初始化Object数组
    this.items = new Object[capacity];
    // 初始化锁(可以指定公平与非公平)
    lock = new ReentrantLock(fair);
    // 非空condition队列,队列空时用于阻塞获取元素的线程
    notEmpty = lock.newCondition();
    // 非满condition队列,队列满时用于阻塞添加元素的线程
    notFull =  lock.newCondition();
}

4.1.3添加元素

add、offer、put三个方法都是添加元素,但是各自的具体实现又不一样,各自的使用场景也不相同。这三个方法在添加元素的时候都会先获得Lock锁,添加成功后释放锁。这里主要介绍put方法

public void put(E e) throws InterruptedException {
    // 检查添加的元素不为空
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 加锁,可异常中断
    lock.lockInterruptibly();
    try {
        // 队列满了
        while (count == items.length)
            // 阻塞继续添加元素,直到唤醒
            // 队列满了之后,当前线程添加元素会阻塞在此位置并释放锁。只有等其他线程移除元素后唤醒当前线程。才可以继续添加元素
            notFull.await();
        // 队列中元素没满,继续添加元素
        enqueue(e);
    } finally {
        // 释放锁
        lock.unlock();
    }
}
private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    // putIndex维护当前元素添加到了哪个位置
    items[putIndex] = x;
    // 
    if (++putIndex == items.length)
        // 重新从0开始计数,前面队列满了会被阻塞,而至少移除一个元素之后才会唤醒添加元素。
        // 而移除元素也是从头开始移除的,所以不会覆盖没有消费的元素
        putIndex = 0;
    // 元素个数+1
    count++;
    // 唤醒获取元素的线程
    // 获取元素时,有可能队列为空。会阻塞再次等待
    notEmpty.signal();
}

4.1.4获取元素

remove、poll、take三个方法都是获取元素,但是各自的具体实现又不一样,各自的使用场景也不相同。这三个方法在获取元素的时候都会先获得Lock锁,获取成功后释放锁。这里主要介绍take方法

peek与element方法与上面的方法又不相同,只会读取元素而不会把他们从队列中移除。

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 加锁,可异常中断
    lock.lockInterruptibly();
    try {
        // 队列为空
        while (count == 0)
            // 阻塞当前线程获取元素
            // 当队列中添加元素时,会唤醒此线程继续去获取队列中的元素
            notEmpty.await();
        // 队列不为空,获取元素
        return dequeue();
    } finally {
        // 释放锁
        lock.unlock();
    }
}
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    // takeIndex维护当前移除元素的位置
    E x = (E) items[takeIndex];
    // 移除元素所在的位置设置为null
    items[takeIndex] = null;
    // 
    if (++takeIndex == items.length)
        // 如果移除元素已经到队列尾部
        // 从0开始去获取元素
        takeIndex = 0;
    // 队列中的元素个数 - 1
    count--;
    // 针对使用迭代器得遍历
    if (itrs != null)
        itrs.elementDequeued();
    // 移除元素之后唤醒添加元素的线程
    notFull.signal();
    return x;
}

使用take方法去阻塞获取元素,当阻塞时使用add方法也会继续take读取,只要是添加元素就会signal

4.2 LinkedBlockingQueue(有界阻塞链表队列)

由链表结构组成的有界阻塞队列,遵循先进先出(FIFO)的原则,和ArrayBlockingQueue的区别是ArrayBlockingQueue内部维护的是一个数组,通过数组下标来维护队列,而LinkedBlockingQueue维护的是一个链表,通过Node来维护队列。同时有两把锁(入队和出队用不同的锁),原子个数count是使用AtomicInteger修饰。

image-20201204002226064

4.2.1 初始化队列

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

public LinkedBlockingQueue(int capacity) {
    // 容量<=0,直接异常
    if (capacity <= 0) throw new IllegalArgumentException();
    // 初始化容量
    this.capacity = capacity;
    // 初始化一个队列,head与last节点为相同的node节点
    last = head = new Node<E>(null);
}

// Node节点,含有下一个节点的引用
static class Node<E> {
    E item;
    
    Node<E> next;

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

image-20201125234637633

4.2.2 添加元素

public void put(E e) throws InterruptedException {
    // 元素为空,异常
    if (e == null) throw new NullPointerException();
    int c = -1;
    // 初始化Node
    Node<E> node = new Node<E>(e);
    // 获得put锁
    final ReentrantLock putLock = this.putLock;
    // 队列的大小,AtomicInteger原子类,并发安全的。
    final AtomicInteger count = this.count;
    // 加锁
    putLock.lockInterruptibly();
    try {
        // 队列满了
        while (count.get() == capacity) {
            // 阻塞其他线程继续添加元素
            notFull.await();
        } 
        // 队列没有满,将Node加入队列
        enqueue(node);
        // 获取count的值,并同时+1
        c = count.getAndIncrement();
        // 如果队列没有满(count还是原队列的大小)c + 1因为此线程put元素加入队列成功。
        if (c + 1 < capacity)
            // 唤醒 其他阻塞添加元素 的线程
            notFull.signal();
    } finally {
        // 释放锁
        putLock.unlock();
    }
    // 说明在添加元素之前,队列是空的。有可能其他线程在获取元素,此时正在被阻塞。这里需要唤醒
    if (c == 0)
        // 唤醒获取元素的线程
        signalNotEmpty();
}
private void enqueue(Node<E> node) {
    // 将添加的元素封装为node加入队列中。head指向空信息节点,不是具体添加的元素 ,但是它拥有添加元素的next引用
    last = last.next = node;
}
private void signalNotEmpty() {
    // 唤醒获取元素的线程
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

image-20201125234657148

4.3.3获取元素

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
        c = count.getAndDecrement();
        // 队列中还存在元素,可以唤醒其他阻塞获取元素的线程去获得元素
        if (c > 1)
            notEmpty.signal();
    } finally {
        // 释放锁
        takeLock.unlock();
    }
    // 队列满时,移除一个后需要唤醒加入元素的线层去添加元素
    // c == capacity 为移除元素之前的容量,移除后,队列肯定不是满的
    if (c == capacity)
        signalNotFull();
    return x;
}
// 把head节点的next节点设置为head,并返回其value值后把item设置为null
private E dequeue() {
    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();
    }
}

image-20201125234932553

4.3LinkedBlockingDeque(有界阻塞双向链表队列)

LinkedBlockingDeque和LinkedBlockingQeque一样是由链表结构组成,但是LinkedBlockingDeque是个双向链表,比LinkedBlockingQeque多一个pre节点的引用。

双向队列因为多了一个操作队列的入口,所以相比较于LinkedBlockingQeque单向队列中多了addFirst、 addLast、offerFirst、offerLast、peekFirst、peekLast、putFirst、putLast、takeFirst、takeLast等方法。另外,插入方法add等同于addLast,移除方法remove等效于removeFirst,而take方法却等同于takeFirst,使用时需要注意。

image-20201126001343289

4.3.1初始化队列

transient Node<E> first;

transient Node<E> last;

/** Number of items in the deque */
private transient int count;

/** Maximum number of items in the deque */
private final int capacity;

/** Main lock guarding all access */
final ReentrantLock lock = new ReentrantLock();

/** Condition for waiting takes */
private final Condition notEmpty = lock.newCondition();

/** Condition for waiting puts */
private final Condition notFull = lock.newCondition();

// 初始化的时候没有设置任何节点,仅仅只是设置了一个容量
public LinkedBlockingDeque(int capacity) {
    // 初始化容量<=0,抛异常
    if (capacity <= 0) throw new IllegalArgumentException();
    // 设置队列的容量
    this.capacity = capacity;
}

static final class Node<E> {
    E item;
	// prev节点
    Node<E> prev;
	// next节点
    Node<E> next;

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

4.3.2添加元素

4.3.2.1 putFirst
public void putFirst(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 (!linkFirst(node))
            notFull.await();
    } finally {
        lock.unlock();
    }
}
// 把当前节点,加入链表的首个位置
private boolean linkFirst(Node<E> node) {
    if (count >= capacity)
        return false;
    Node<E> f = first;
    node.next = f;
    first = node;
    if (last == null)
        last = node;
    else
        f.prev = node;
    ++count;
    notEmpty.signal();
    return true;
}
4.3.2.1 putLast
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();
    }
}
private boolean linkLast(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    if (count >= capacity)
        return false;
    Node<E> l = last;
    node.prev = l;
    last = node;
    if (first == null)
        first = node;
    else
        l.next = node;
    ++count;
    notEmpty.signal();
    return true;
}

4.3.3获取元素

4.3.3.1takeFirst
public E takeFirst() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        while ( (x = unlinkFirst()) == null)
            notEmpty.await();
        return x;
    } finally {
        lock.unlock();
    }
}
private E unlinkFirst() {
    // assert lock.isHeldByCurrentThread();
    Node<E> f = first;
    if (f == null)
        return null;
    Node<E> n = f.next;
    E item = f.item;
    f.item = null;
    f.next = f; // help GC
    first = n;
    if (n == null)
        last = null;
    else
        n.prev = null;
    --count;
    notFull.signal();
    return item;
}
4.3.3.2takeLast
public E takeLast() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        while ( (x = unlinkLast()) == null)
            notEmpty.await();
        return x;
    } finally {
        lock.unlock();
    }
}
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;
}

image-20201126232921461

4.4PriorityBlockingQueue(无界阻塞优先队列)

PriorityBlockingQueue是一个支持优先级的无界阻塞队列(大小受限于内存)。和前面介绍的三种有界队列相比,无界队列的最大区别是即使初始化的时候指定了长度,那么当队列元素达到上限后队列也会自动进行扩容,所以PriorityBlockingQueue在添加元素的时候不会发生阻塞,而如果扩容后的大小超过了内存限制,会抛出OutOfMemoryError错误。

学习PriorityBlockingQueue之前,我们需要先学习下二叉堆相关知识。可参考下面博客:

数据结构:二叉堆_wangbo199308的博客-CSDN博客

二叉堆用数组来实现。

  • 节点的左子节点是 2*index+1
  • 节点的右子节点是 2*index+2
  • 节点的父节点是 (index-1)/2
  • 叶子节点的下标为index[n/2]到index[n-1]。如一个长度为13的数组中,index[6]到index[12]位置的元素均属于叶子节点。

默认情况下PriorityBlockingQueue队列元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者在初始化时,可以指定构造参数Comparator来对元素进行排序。
注意:PriorityBlockingQueue不能保证相同优先级元素的顺序(即两个值排序一样时,不保证顺序)。类图如下:

image-20201126235210758

可以看到提供了4个 构造器:

  • PriorityBlockingQueue():
    初始化一个默认大小(11)长度的队列,并使用默认自然排序。
  • PriorityBlockingQueue(int):
    初始化一个指定大小的长度的队列,并使用默认自然排序。
  • PriorityBlockingQueue(int,Comparator):
    初始化一个指定大小的队列,并按照指定比较器进行排序。
  • PriorityBlockingQueue(Collection):
    根据传入的集合进行初始化并堆化,如果当前集合是SortedSet或者PriorityBlockingQueue类型,则保持原有顺序,否则使用自然排序进行堆化。

默认情况下元素采用自然顺序升序排序,可以通过指定Comparator来对元素进行排序

4.4.1初始化

private transient Object[] queue;

/**
 * The number of elements in the priority queue.
 */
private transient int size;

/**
 * The comparator, or null if priority queue uses elements'
 * natural ordering.
 */
private transient Comparator<? super E> comparator;

/**
 * Lock used for all public operations
 */
private final ReentrantLock lock;

/**
 * 出队,队列为空条件队列
 */
private final Condition notEmpty;

/**
 * 扩容标记
 */
private transient volatile int allocationSpinLock;
public PriorityBlockingQueue(int initialCapacity,
                             Comparator<? super E> comparator) {
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.lock = new ReentrantLock();
    this.notEmpty = lock.newCondition();
    this.comparator = comparator;
    this.queue = new Object[initialCapacity];
}

我们看到只有一个Condition队列,这个是用来阻塞出队线程的,入队线程不会被阻塞。

接下来我们主要看看第4个构造器,是如何初始化一个队列的:

public PriorityBlockingQueue(Collection<? extends E> c) {
    this.lock = new ReentrantLock();
    this.notEmpty = lock.newCondition();
    // true表示需要堆化即需要重排序
    boolean heapify = true; // true if not known to be in heap order
    // true表示需要筛选空值
    boolean screen = true;  // true if must screen for nulls
    // 如果集合是SortedSet类型则不需要堆化
    if (c instanceof SortedSet<?>) {
        SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
        this.comparator = (Comparator<? super E>) ss.comparator();
        heapify = false;
    }
    // 如果集合是PriorityBlockingQueue类型则不需要筛选空值
    else if (c instanceof PriorityBlockingQueue<?>) {
        PriorityBlockingQueue<? extends E> pq =
            (PriorityBlockingQueue<? extends E>) c;
        this.comparator = (Comparator<? super E>) pq.comparator();
        screen = false;
        // 如果pq就是一个PriorityBlockingQueue则不需要堆化
        if (pq.getClass() == PriorityBlockingQueue.class) // exact match
            heapify = false;
    }
    // 把集合转为
    Object[] a = c.toArray();
    // 集合的长度
    int n = a.length;
    // //如果c.torray()失败,重新复制一个数组
    if (a.getClass() != Object[].class)
        a = Arrays.copyOf(a, n, Object[].class);
    if (screen && (n == 1 || this.comparator != null)) {
        for (int i = 0; i < n; ++i)
            if (a[i] == null)
                throw new NullPointerException();
    }
    this.queue = a;
    this.size = n;
    // 初始化集合需要排序符合二叉堆的特性
    if (heapify)
        // 堆化(排序)
        heapify();
}
private void heapify() {
    // 二叉堆数据
    Object[] array = queue;
    int n = size;
    // 叶子节点为在数组中的下标为(n/2)到(n-1)
    // 非叶子节点的最后元素下标为(n/2)-1
    // 初始化时,叶子节点为子节点,不需要进行下沉操作
    int half = (n >>> 1) - 1;
    // 比较器
    Comparator<? super E> cmp = comparator;
    if (cmp == null) {
        for (int i = half; i >= 0; i--)
            // 默认比较器下沉
            siftDownComparable(i, (E) array[i], array, n);
    }
    else {
        for (int i = half; i >= 0; i--)
            // 指定比较器下沉
            siftDownUsingComparator(i, (E) array[i], array, n, cmp);
    }
}

下面初始化一个数组queue [8,5,2,7,6,4,1,9,3],初始化为二叉堆的一个步骤。二叉堆的初始化会从最后一个非叶子节点开始,在数组中的下标为(n >>> 1) - 1。

从queue[3]=7这个元素向前循环下沉操作。需要下沉四次

image-20201128164842931

这里介绍默认比较器下沉,siftDownUsingComparator与siftDownComparable几乎差不多。

// k:下沉元素的下标
// x:下沉元素的值
// array:二叉堆数组
// n:二叉堆数组元素的个数
private static <T> void siftDownComparable(int k, T x, Object[] array,
                                           int n) {
    // 元素个数大于0
    // 入队的第一个元素不做任何操作,就是二叉堆的根
    if (n > 0) {
        Comparable<? super T> key = (Comparable<? super T>)x;
        // 第一个叶子节点的下标
        int half = n >>> 1;           // loop while a non-leaf
        while (k < half) {
            // 当前节点K的左子节点2K+1
            int child = (k << 1) + 1; // assume left child is least
            // 左子节点的值
            Object c = array[child];
            // 当前节点K的右子节点2K+2
            int right = child + 1;
            // 如果左子节点大于右子节点
            if (right < n &&
                ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                // 先将两个子节点替换(只是临时变量替换,实际不会替换)
                // 找出compare后的最小子节点
                c = array[child = right];
            // 如果当前节点小于替换后的左子节点,跳出循环
            if (key.compareTo((T) c) <= 0)
                break;
            // 当前节点大于替换后的左子节点,则将替换后的左子节点赋值给父节点
            array[k] = c;
            // 子节点最小的元素下标。循环判断子节点也需要下沉操作
            k = child;
        }
        // 将父节点赋值给子节点
        array[k] = key;
    }
}

上面的主要逻辑为:

  • 将当前循环节点的左右子节点比较,确保拿到最小子节点的下标child
  • 再将child对应的元素和父节点比较,确保父节点<最小子节点
  • 最后会再次确认当前元素与最小子节点(可能是左也可能是右)的子节点(如果有的话)进行大小比较,依此类推,完成元素下沉。

排序后为:

image-20201128174309296

4.4.2添加元素

入队,首先检查有没有达到数组容量的上限。达到后进行扩容。之后把元素添加到数据最后进行上浮。

public void put(E e) {
    offer(e); // never need to block
}
public boolean offer(E e) {
    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;
        // 指定比较器为空
        if (cmp == null)
            // 默认比较器元素上浮
            siftUpComparable(n, e, array);
        else
            // 指定比较器元素上浮
            siftUpUsingComparator(n, e, array, cmp);
        // 添加成功后,元素个数+1
        size = n + 1;
        // 唤醒出队线程
        notEmpty.signal();
    } finally {
        // 释放锁
        lock.unlock();
    }
    // 添加成功,返回true
    return true;
}
4.4.2.1上浮
private static <T> void siftUpComparable(int k, T x, Object[] array) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    while (k > 0) {
        // 父节点
        int parent = (k - 1) >>> 1;
        // 父节点值
        Object e = array[parent];
        // 当前入队节点大于父节点,break退出,不做任何操作
        if (key.compareTo((T) e) >= 0)
            break;
        // 当前入队节点小于父节点,将父节点替换到末尾。
        array[k] = e;
        // 继续把当前节点的父节点进行上浮比较
        k = parent;
    }
    array[k] = key;
}
4.4.2.2扩容
private void tryGrow(Object[] array, int oldCap) {
    // 扩容前先释放锁(扩容可能会费时,先让出锁,让出队线程可以正常操作)
    lock.unlock(); // must release and then re-acquire main lock
    Object[] newArray = null;
    // CAS操作,只有一个线程可以扩容
    if (allocationSpinLock == 0 &&
        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                 0, 1)) {
        try {
            // 原始容量小于64时扩容增加oldCap + 2
            // 原始容量大于等于64时扩容增加oldCap/2
            int newCap = oldCap + ((oldCap < 64) ?
                                   (oldCap + 2) : // grow faster if small
                                   (oldCap >> 1));
            // 大于当前最大容量则可能溢出
            if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow
                // 扩大一个元素超出Integer最大值或超过最大容量则抛出异常
                int minCap = oldCap + 1;
                if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                    throw new OutOfMemoryError();
                // 容量初始化为MAX_ARRAY_SIZE
                newCap = MAX_ARRAY_SIZE;
            }
            // 根据扩容后容量初始化一个新数组
            if (newCap > oldCap && queue == array)
                newArray = new Object[newCap];
        } finally {
            // 扩容标识重置为0,可以进行下次扩容
            allocationSpinLock = 0;
        }
    }
    // 扩容新数组,前面CAS失败,有线程在扩容,让出CPU
    if (newArray == null) // back off if another thread is allocating
        Thread.yield();
    // 这里重新加锁是确保数组复制操作只有一个线程能进行
    lock.lock();
    if (newArray != null && queue == array) {
        queue = newArray;
        System.arraycopy(array, 0, newArray, 0, oldCap);
    }
}

4.4.3获取元素

  • 先拿到第一个元素(需要返回)和最后一个元素
  • 然后将最后一个元素置为空
  • 用存好的最后一个元素的值从头开始下沉
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lockInterruptibly();
    E result;
    try {
        // 队列为空
        while ( (result = dequeue()) == null)
            // 出队阻塞,等待元素的入队
            notEmpty.await();
    } finally {
        // 释放锁
        lock.unlock();
    }
    return result;
}
private E dequeue() {
    // 元素个数-1
    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;
        Comparator<? super E> cmp = comparator;
        if (cmp == null)
            // 将数组元素的最后一个节点放在数组头部进行下沉操作
            // 初始化时分析过
            siftDownComparable(0, x, array, n);
        else
            siftDownUsingComparator(0, x, array, n, cmp);
        size = n;
        return result;
    }
}

4.5DelayQueue(无界阻塞延迟队列)

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

DelayQueue非常有用, 可以将DelayQueue运用在以下应用场景。

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

Demo:延迟获取元素,每个元素延迟的时间不同。

public class DelayQueueDemo {

    private static DelayQueue<DelayedInteger> delayQueue = new DelayQueue<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 5; i++) {
            // 入队5个元素,每个延迟时间不同
            DelayedInteger myElement = new DelayedInteger(i, i * 10000);
            delayQueue.put(myElement);
        }

        while (true) {
            DelayedInteger element = delayQueue.take();
            System.out.println(element.getElement());
            if (delayQueue.size() == 0) {
                break;
            }
        }
    }

    static class DelayedInteger implements Delayed {

        private int element;

        private long expireTime;

        public DelayedInteger() {

        }

        public DelayedInteger(int element, long expireTime) {
            this.element = element;
            this.expireTime = System.currentTimeMillis() + expireTime;
        }

        public int getElement() {
            return element;
        }

        public void setElement(int element) {
            this.element = element;
        }

        public long getExpireTime() {
            return expireTime;
        }

        public void setExpireTime(long expireTime) {
            this.expireTime = expireTime;
        }

        // 该方法返回当前元素还需要延迟多长时间,单位是纳秒
        @Override
        public long getDelay(TimeUnit unit) {
            //类里面接收的是毫秒,但是getDelay方法在DelayQueue里面传的是纳秒,所以这里需要进行一次单位转换
            return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            // 这里的排序要确定最先到期的放在第一位,否则会阻塞住后面已经到期的
            // 当先到期的被未到期的阻塞,未到期的出队后之前先到期的不用再延迟
			return Long.valueOf(expireTime).compareTo(Long.valueOf(((DelayedInteger) o).getExpireTime()));
        }
    }
}

类图:

image-20201128232343717

4.5.1初始化

DelayQueue队列的元素是存在其内部维护的PriorityQueue。

// 锁,保证线程安全
private final transient ReentrantLock lock = new ReentrantLock();
// 无界阻塞优先队列,用于存储元素。初始化为最大值,不存在扩容情况
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 表示正在等待到期时间的线程
private Thread leader = null;
private final Condition available = lock.newCondition();
public DelayQueue() {}

public DelayQueue(Collection<? extends E> c) {
    this.addAll(c);
}

4.5.2添加元素

无界阻塞队列,添加元素不会阻塞。添加元素是把元素加入优先队列priorityQueue中

public void put(E e) {
    offer(e);
}
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();
        }
        return true;
    } finally {
    	// 释放锁
        lock.unlock();
    }
}
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    // 扩容
    if (i >= queue.length)
        grow(i + 1);
    // 元素个数+1
    size = i + 1;
    // 入队第一个元素,放在数组第一个位置
    if (i == 0)
        queue[0] = e;
    else
        // 不是优先队列的第一个元素,需要执行上浮操作
        siftUp(i, e);
    return true;
}

4.5.3获取元素

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            // 获得队头元素
            E first = q.peek();
            // 队列为空,则阻塞。
            // 入队时,判断是否是第一个元素入队,此时唤醒出队
            if (first == null)
                available.await();
            else {
                // 获取出队延迟时间
                long delay = first.getDelay(NANOSECONDS);
                // 如果到期了,则调用poll方法取元素并直接返回
                if (delay <= 0)
                    return q.poll();
                first = null;
                // 有头结点线程正在等待到期时间,所以直接阻塞
                if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    // 设置头结点线程
                    leader = thisThread;
                    try {
                        // 阻塞指定时间
                        available.awaitNanos(delay);
                    } finally {
                        // 设置头结点线程为null
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        // 头结点为空,并且队列中还有元素。
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

4.5.4Leader-Follower线程模型

在Leader-follower线程模型中每个线程有三种模式:

  • leader:只有一个线程成为leader,如DelayQueue如果有一个线程在等待元素到期,则其他线程就会阻塞等待
  • follower:会一直尝试争抢leader,抢到leader之后才开始干活
  • processing:处理中的线程

DelayQueue队列中有一个leader属性:private Thread leader = null;

用到的就是Leader-Follower线程模型。

当有一个线程持有锁,设置了leader属性,正在等待元素到期时,则成为了leader,其他线程就直接阻塞。

出队流程图如下:

image-20201129010155896

4.6SynchronousQueue(不存储元素的阻塞队列)

SynchronousQueue是一个不存储元素的阻塞队列,每一个入队操作必须等待一个出队操作,否则不能继续添加元素。其没有任何内部容量。

  • put(E)操作时,队列中的节点代表一个元素,也就说表示的是数据
  • take()操作时,如果队列中无元素,会放一个null的项到队列中占位,这时候表示的是一个请求,而不是一个数据。
public E peek() {
    return null;
}

/**
 * Returns an empty iterator in which {@code hasNext} always returns
 * {@code false}.
 *
 * @return an empty iterator
 */
public Iterator<E> iterator() {
    return Collections.emptyIterator();
}
public boolean isEmpty() {
    return true;
}

/**
 * Always returns zero.
 * A {@code SynchronousQueue} has no internal capacity.
 *
 * @return zero
 */
public int size() {
    return 0;
}

不能执行peek()、element()之类的方法获取一个元素,因为一个元素只有在尝试移除的时候(remove、poll)才会出现,也不能使用任何方法去插入一个元素,除非另一个线程正好在尝试移除它(take),也不能去迭代,因为没有任何东西可以迭代。

  • 对于add、offer两个不阻塞入队操作,必须有出队线程take操作,才能成功添加当前元素。不然add抛出异常,offer返回false
  • 对于put阻塞入队操作,必须有出队线程的出队,才能成功添加元素。
  • 对于take阻塞出队操作,必须有入队线程的入队操作,才能成功移除元素。

SynchronousQueue的head是第一个排队插入线程试图添加到队列的元素,如果没有这样的排队线程那么就没有元素可以被移除,所以执行poll()的时候就会返回null。

SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。

SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue,因为其内部是通过CAS和自旋来实现并发,没有通过锁来控制,减少了锁的开销。

image-20201129220004650

4.6.1初始化

SynchronousQueue提供了两个构造器,默认构造器是非公平策略,可以通过第二个构造器传入参数true来构造一个公平策略

public SynchronousQueue() {
    this(false);
}
public SynchronousQueue(boolean fair) {
    transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
// TransferQueue的构造方法
TransferQueue() {
    QNode h = new QNode(null, false); // initialize to dummy node.
    head = h;
    tail = h;
}

TransferQueue内部是通过QNode节点来维持一个队列,QNode是TransferQueue的一个内部类:

static final class QNode {
    // 下一个节点
    volatile QNode next;          // next node in queue
    // 节点中的值,通过CAS操作。将空变为非空(put),将非空变为空(take)
    volatile Object item;         // CAS'ed to or from null
    volatile Thread waiter;       // to control park/unpark
    // false表示take,true表示put
    final boolean isData;
    QNode(Object item, boolean isData) {
        this.item = item;
        this.isData = isData;
    }
	// ...
    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);
        }
    }
}

image-20201129222209218

4.6.2.添加元素

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();
    }
}
4.6.2.1.transfer
// timed:false表示put,true表示take
E transfer(E e, boolean timed, long nanos) {

    QNode s = null;
    // e==null表示当前是消费者(take操作),e!=null表示当前是生产者(put操作)
    boolean isData = (e != null);

    for (;;) {
        // 头结点(初始化时item为空的节点)
        QNode t = tail;
        // 尾节点
        QNode h = head;
        // 表示还没有初始化(初始化之后不可能为空),继续自旋
        if (t == null || h == null)         
            continue;                       
		// head=tail或者tail节点的模式和当前操作模式相同
        if (h == t || t.isData == isData) { 
            // 获得当前tail节点的next节点
            QNode tn = t.next;
            // t和tail不一致,说明有其他线程操作过,继续自旋
            if (t != tail)                  
                continue;
            // tail.next正常是null,不为null说明其他线程新增了元素到tail.next
            if (tn != null) {  
                // 尝试帮助其他线程将tail.next设置为tail,然后继续自旋
                advanceTail(t, tn);
                continue;
            }
            // take操作。如果当前调用的是超时方法,且到时间了,直接返回null
            if (timed && nanos <= 0)
                return null;
            // 第一次进来肯定是null。当cas next与tail两个操作失败时,自旋进入此时已经初始化过不为空
            if (s == null)
                // 如果是put进来,e不为null,如果是take进来,e==null,也就是初始化了一个空节点
                s = new QNode(e, isData);
            // CAS设置tail节点的next节点,cas失败(并发竞争失败)继续自旋
            if (!t.casNext(null, s))        // failed to link in
                continue;
			// 将s设置为新的tail节点
            // 这里是一定会成功的,因为上面的cas完成之后,这一步不执行完成,其他线程只能一直自旋等待
            advanceTail(t, s);              
            // 节点添加进去之后,阻塞等待,直接消费者线程来消费
            // 被正常take,返回为null
            Object x = awaitFulfill(s, e, timed, nanos);
            // x==s表示put线程已经中断
            if (x == s) {    
                // 清除
                clean(t, s);
                // 直接返回null
                return null;
            }

            if (!s.isOffList()) {           // not already unlinked
                advanceHead(t, s);          // unlink if head
                if (x != null)              // and forget fields
                    s.item = s;
                s.waiter = null;
            }
            return (x != null) ? (E)x : e;

        } else {                            // complementary-mode
            QNode m = h.next;               // node to fulfill
            if (t != tail || m == null || h != head)
                continue;                   // inconsistent read

            Object x = m.item;
            if (isData == (x != null) ||    // m already fulfilled
                x == m ||                   // m cancelled
                !m.casItem(x, e)) {         // lost CAS
                advanceHead(h, m);          // dequeue and retry
                continue;
            }

            advanceHead(h, m);              // successfully fulfilled
            LockSupport.unpark(m.waiter);
            return (x != null) ? (E)x : e;
        }
    }
}
4.6.2.2advanceTail
// cas设置尾节点
void advanceTail(QNode t, QNode nt) {
    if (tail == t)
        UNSAFE.compareAndSwapObject(this, tailOffset, t, nt);
}
4.6.2.3awaitFulfill
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
    /* Same idea as TransferStack.awaitFulfill */
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    // 获取当前线程
    Thread w = Thread.currentThread();
    // 自旋次数
    int spins = ((head.next == s) ?
                 (timed ? maxTimedSpins : maxUntimedSpins) : 0);
    // 自旋
    for (;;) {
        // 线程中断
        if (w.isInterrupted())
            // 线程中断后,会将s中的item变为QNode s
            s.tryCancel(e);
        // 当前节点的item
        Object x = s.item;
        // 相等说明被取消或者值已经被取走
        if (x != e)
            return x;
        if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                s.tryCancel(e);
                continue;
            }
        }
        // 自旋
        if (spins > 0)
            --spins;
        // 下次自旋设置等待线程
        else if (s.waiter == null)
            s.waiter = w;
        // 如果当前方法不是带超时时间的,则直接挂起直到唤醒
        else if (!timed)
            LockSupport.park(this);
        else if (nanos > spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanos);
    }
}
4.6.2.4tryCancel
void tryCancel(Object cmp) {
    UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
}

A、B、c三个线程put数据

image-20201203234524430

4.6.3移除元素(已经PUT)

public E take() throws InterruptedException {
    E e = transferer.transfer(null, false, 0);
    if (e != null)
        return e;
    Thread.interrupted();
    throw new InterruptedException();
}
4.6.3.1transfer
E transfer(E e, boolean timed, long nanos) {
   
    QNode s = null;
    // take数据,e为null, isData为null
    // put为true,take为false
    boolean isData = (e != null);

    for (;;) {
        QNode t = tail;
        QNode h = head;
        if (t == null || h == null)         // saw uninitialized value
            continue;                       // spin
		// take元素前已经PUT不会走此分支
        if (h == t || t.isData == isData) { // empty or same-mode
            // ....

        } else {
            // 互补模式
            // 先put再take走此分支
            // 获取头结点后的第一个节点
            QNode m = h.next;
            // 不一致情况,继续下一次自旋
            if (t != tail || m == null || h != head)
                continue;                   // inconsistent read
			// 拿到节点中的item
            Object x = m.item;
            // 元素已经被其他元素拿走了(item变为null),继续自旋
            // 说明线程put已经被中断
            if (isData == (x != null) ||    
                x == m ||
                // take操作把m节点的item变为null
                !m.casItem(x, e)) { 
                // 上面任意一个条件失败都说明当前元素已经不存在,所以帮助那个线程cas替换一下头节点
                advanceHead(h, m);  
                // 自旋处理下一个节点
                continue;
            }
			// 说明元素是被当前线程取走了,cas替换一下头节点
            advanceHead(h, m);              
            // 唤醒阻塞put元素的线程继续传递元素
            LockSupport.unpark(m.waiter);
            // 返回当前元素
            return (x != null) ? (E)x : e;
        }
    }
}

之后唤醒put元素的线程,还在transfer中

image-20201203235717508

4.6.4先take再put

上面已经分析了,先put元素在take元素。

下面简单介绍下先take再put。主要画出put与take操作的图,代码自行分析

image-20201204000135987

先take再put与先put再take为互补模式。只不过 take返回数据的位置不一样。

image-20201204001801131

4.6.5总结

主要区别如下:

  • put —>take

    • put
    1. 将元素初始化成为一个QNode。

    2. 将tail.next指向当前新构建的QNode(CAS操作)。

    3. 将新构建的QNode设置为tail节点(CAS操作)。

    4. 将当前QNode节点中的waiter属性设置为当前线程(awaitFulfill方法)。

    5. 挂起前线程(awaitFulfill方法)。

    • take
    1. 将head.next中的item设置为null(CAS操作)。

    2. 将head.next设置为新的head节点(advanceHead方法)。

    3. 将原head节点的next指向自己(advanceHead方法)。

    4. 通过原节点的waiter属性,将原先线程唤醒。

    5. 返回获取到的元素(take线程transfer的else分支)。

  • take—>put

    • take
    1. 将一个null元素初始化成为一个QNode

    2. 将tail.next指向当前新构建的QNode(CAS操作)。

    3. 将新构建的QNode设置为tail节点(CAS操作)。

    4. 将当前QNode节点中的waiter属性设置为当前线程(awaitFulfill方法)。

    5. 挂起前线程(awaitFulfill方法)。

    • put
    1. 将head.next中的item设置为1(CAS操作)。

    2. 将head.next设置为新的head节点(advanceHead方法)。

    3. 将原head节点的next指向自己(advanceHead方法)。

    4. 通过原节点的waiter属性,将原先线程唤醒。

    5. 返回成功put进去到的元素。(take线程transfer的if分支)

4.7LinkedTransferQueue(无界阻塞链表队列)

LinkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法。LinkedTransferQueue和SynchronousQueue中的公平策略使用的算法是一样的,唯一的区别是SynchronousQueue内部不会存储哪怕1个元素,而LinkedTransferQueue内部会存储元素。

TransferQueue扩展的transfer方法会一直阻塞直到添加的数据被消费者消费,这点与上一篇学的SynchronousQueue的put方法很相似,所以LinkedTransferQueue具有SynchronousQueue的功能。

松弛度正常队列中,当移除一个元素的时候,就会同步移动head和tail节点的指针,为了最大程序的保证性能LinkedTransferQueue不会实时去更新head和tail的指针,而是引入了一个松弛度的概念。**松弛度指的是head值与第一个不匹配节点之间的目标最大距离,反之对tail也是如此。**这个值这一般为1-3(根据经验得出),在LinkedTransferQueue中松弛度定义为2。因为如果太大了会增加缓存丢失的成本或者长遍历链的风险,而较小的话就会增加CAS的开销。

image-20201206225519973

相比较于其他阻塞队列,多了一个TransferQueue接口,我们先来看看TransferQueue接口中核心的几个方法:

image-20201206225624211

4.7.1初始化

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

public LinkedTransferQueue() {
}
public LinkedTransferQueue(Collection<? extends E> c) {
    this();
    addAll(c);
}

public boolean addAll(Collection<? extends E> c) {
    if (c == null)
        throw new NullPointerException();
    if (c == this)
        throw new IllegalArgumentException();
    boolean modified = false;
    for (E e : c)
        if (add(e))
            modified = true;
    return modified;
}

初始化的时候什么也不做,并不会在内部构造一个初始节点。addAll()实际上也是循环调用了add(E)方法:

4.7.2xfer

通过查看源码发现put、offer、add、take、poll方法包括tryTransfer、transfer都是调用的xfer方法。所以我们重点分析xfer方法。

  • e表示要添加的数据,take与poll为null

  • haveData表示是否有数据,添加类方法为true,消费类为false

  • how表示方法阻塞方式,LinkedTransferQueue定义了4个静态变量NOW、ASYNC、SYNC、TIMED

    • NOW表示不阻塞在poll、tryTransfer方法使用:for untimed poll, tryTransfer
    • ASYNC在put、offer、add方法使用:for offer, put, add
    • SYNC表示阻塞用于take方法:for transfer, take
    • TIMED用于poll、tryTransfer的延时方法:for timed poll, tryTransfer
  • nanos表示最大阻塞多少时间,poll和tryTransfer方法会用到

private E xfer(E e, boolean haveData, int how, long nanos) {
        if (haveData && (e == null))//如果当前是put操作,且e==null,则抛出异常
            throw new NullPointerException();
        Node s = null;                        // the node to append, if needed

        retry:
        for (;;) {                            // restart on append race
            //从head开始循环匹配
            for (Node h = head, p = h; p != null;) { // find & match first node
                boolean isData = p.isData;
                Object item = p.item;
                //如果元素还没被匹配过,也就是还在队列里
                if (item != p && (item != null) == isData) { // unmatched
                    //如果相等,就说明是两个相同操作,直接不用执行后面了,互补操作才能往后走
                    if (isData == haveData)   // can't match
                        break;
                    //将p中的item替换成e
                    if (p.casItem(item, e)) { // match
                        //假如有一个队列是有元素的,第一次被take()的时候,q==h是进不了for循环的,所以会直接返回
                        //第2次进来会先匹配一次head节点,匹配不上,在匹配第2个节点,这就相当于松弛度=2了,所以
                        //这时候是满足条件的,可以进入for循环,
                        for (Node q = p; q != h;) {
                            Node n = q.next;  // update by 2 unless singleton
                            //移动head指针
                            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
                        }
                        LockSupport.unpark(p.waiter);
                        return LinkedTransferQueue.<E>cast(item);
                    }
                }
                Node n = p.next;
                p = (p != n) ? n : (h = head); // Use head if p offlist
            }

            if (how != NOW) {                 // No matches available
                if (s == null)
                    s = new Node(e, haveData);//初始化节点
                Node pred = tryAppend(s, haveData);
                if (pred == null)
                    continue retry;           // lost race vs opposite mode
                if (how != ASYNC)//take()或者带超时时间的方法会走这里
                    return awaitMatch(s, pred, e, (how == TIMED), nanos);
            }
            return e; // not waiting
        }
    }

这个方法也要分为两种方式,先put(E)再take()和先take()再put(E)。

先put(E)再take()

put(E)操作不会进行阻塞,成功之后直接返回。

线程t1过来put(1)

因为head和tail都是null(一开始不会初始化队列),所以上面的第2个for循环是进不去的,会走到后面这里初始化node,并加入到队列中,最终得到如下队列:
在这里插入图片描述
可以看到,这时候tail节点并没有被初始化,这是因为利用了松弛度,松弛度要等于2才会移动tail指针(这是一种性能的优化),我们看看tryAppend方法(主要是看红框部分):
在这里插入图片描述
主要分为以下步骤:

  • 1、初始化Node节点
  • 2、将Node节点设为head节点
线程t2过来put(2)

线程t2再进来put的时候,因为满足松弛度=2了,这时候就会移动tail指针,所以会得到如下队列:
在这里插入图片描述
主要分为以下步骤:

  • 1、初始化Node节点
  • 2、将Node节点设为head.next节点
  • 3、达到松弛度,将新Node设置为tail节点
    后面如果再有元素过来添加,到第3个元素的时候,tail也是不会移动的,要第3个元素才会移动tail,这里就不再继续举例了。
线程t3过来take()

这时候来take会将head节点的元素设为null,然后直接返回,得到如下队列:
在这里插入图片描述
这时候因为松弛度还没达到2,不会移动head指针。
主要经过如下步骤:

  • 1、将head节点中的item设置为null。
  • 2、返回获取到的item,。
线程t4过来take()

这时候首先会循环head节点,发现不匹配,然后循环到head.next,得到如下队列:
在这里插入图片描述
这里因为松弛度达到2,所以会移动head指针。
主要经过如下步骤:

  • 1、循环head节点,发现不匹配。
  • 2、循环head.next,匹配上,将head.next中item设置为null。
  • 3、移动head指针,指向head.next。

先take()再put(E)

先take()的时候,因为队列中还没有元素,所以会先自旋,自旋一定次数之后就阻塞,直到有元素put(E)进来然后唤醒线程。

线程t1过来take()

和上面第一次put(E)一样,上面的第2个for循环进不去,会走到后面这里初始化node,并加入到队列中,最终得到如下队列:
在这里插入图片描述
主要经过如下步骤:

  • 1、初始化一个item=null的Node节点。
  • 2、将Node节点设置为head节点。
  • 3、自旋一定次数(awaitMatch方法)。
  • 4、达到自旋次数后还没有线程过来take(),执行park挂起线程(awaitMatch方法)。

注意,上面的Node中也有一个waier属性用来存储线程信息,后面唤醒需要获取waiter中的线程

线程t2过来take()

这时候因为松弛度达到2了,会移动tail指针,最终得到如下队列:
在这里插入图片描述

线程t3过来put(1)

这时候因为前面take()的时候,队列中已经有了item=null的元素(node!=null),所以会直接进入第2个for循环,然后将值替换进head节点中的item,最后唤醒t1线程,t1线程被唤醒之后,会继续自旋,然后返回拿到t3线程put进来的元素:
在这里插入图片描述
最终得到如下队列:
在这里插入图片描述
主要经过如下步骤:

  • 1、将head节点中的item替换成当前元素1。
  • 2、唤醒t1线程。
  • 3、t1线程将head节点中的item指向当前自己的Node,并将waiter设置为null。
  • 4、t1拿到put进来的元素1返回。
线程t4过来put(2)

这时候主要流程和上面一样,但是因为松弛度达到了2,所以会移动head节点指针,最终得到如下队列:
在这里插入图片描述
主要步骤为:

  • 1、循环head节点,发现head已经被匹配过了(item=p)。
  • 2、继续循环head.next,这时候发现可以匹配上,将head.next中的item设置为2。
  • 3、这时候因为松弛度达到2,会将head节点后移。
  • 4、将旧head节点的next指向当前自己的节点(forgetNext方法)。
  • 5、唤醒线程t2。
  • 6、t2线程将新head节点中的item指向当前自己的Node,并将waiter设置为null。
  • 7、线程t2拿到元素2返回
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值