JUC探险-15、BlockingQueue

18 篇文章 0 订阅

文章目录

一、:BlockingQueue简介

  在常见的"生产者-消费者"问题中,队列通常被视作线程间操作数据的容器,这样可以实现对各个模块的业务功能进行解耦。生产者将数据放置在容器中,而消费者仅需要在容器中进行获取即可,这样生产者线程和消费者线程就能够进行解耦,只专注于自己的业务功能。

  阻塞队列(BlockingQueue)被广泛应用在“生产者-消费者”问题中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。

  常见的BlockingQueue

    实现BlockingQueue接口的有ArrayBlockingQueue、PriorityBlockingQueue、DelayQueue、SynchronousQueue、LinkedTransferQueue、LinkedBlockingDeque、LinkedBlockingQueue,而这几种常见的阻塞队列也是在实际编程中常用的。


二、:ArrayBlockingQueue

  ①ArrayBlockingQueue简介

    ArrayBlockingQueue是由数组实现的有界阻塞队列。该队列命令元素FIFO(先进先出)。因此,对头元素时队列中存在时间最长的数据元素,而对尾数据则是当前队列最新的数据元素。ArrayBlockingQueue可作为“有界数据缓冲区”,生产者存入数据到队列容器中,并由消费者提取。ArrayBlockingQueue一旦创建,容量不能改变
    当队列容量时,尝试将元素放入队列将导致操作阻塞。当尝试从一个空队列一个元素同样会阻塞。
    ArrayBlockingQueue默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到ArrayBlockingQueue。而非公平性则是指访问ArrayBlockingQueue的顺序不是遵守严格的时间顺序,有可能存在,一旦ArrayBlockingQueue可以被访问时,长时间阻塞的线程依然无法访问到ArrayBlockingQueue。(如果保证公平性,通常会降低吞吐量)

  ②ArrayBlockingQueue的关键属性

// 使用数组存储元素
final Object[] items;

// 取元素的指针
int takeIndex;

// 存元素的指针
int putIndex;

// 元素数量
int count;

// 锁
final ReentrantLock lock;

// 非空条件
private final Condition notEmpty;

// 非满条件
private final Condition notFull;

    通过观察属性,我们可以看到几个重要信息:
      ●使用数组存储元素。
      ●通过存指针和取指针来标记下一次操作的位置。
      ●利用ReentrantLock来保证并发安全。
      ●利用Condition作为多线程中消息通知机制。(当获取数据的消费者线程被阻塞时会将该线程放置到notEmpty等待队列中,当存入数据的生产者线程被阻塞时,会将该线程放置到notFull等待队列中)

  ③ArrayBlockingQueue的重点方法分析

    1、构造方法

// 指定容量,默认非公平
public ArrayBlockingQueue(int capacity) {
    this(capacity, false);
}

// 指定容量,指定是否公平
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    // 初始化数组
    this.items = new Object[capacity];
    // 创建锁及两个Condition
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

// 指定容量,指定是否公平,使用指定集合内容
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) {
    this(capacity, fair);

    final ReentrantLock lock = this.lock;
    lock.lock(); // Lock only for visibility, not mutual exclusion
    try {
        int i = 0;
        try {
            for (E e : c) {
                checkNotNull(e);
                items[i++] = e;
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new IllegalArgumentException();
        }
        count = i;
        putIndex = (i == capacity) ? 0 : i;
    } finally {
        lock.unlock();
    }
}

    2、存数据方法

public boolean add(E e) {
	// 调用父类的add()方法
    return super.add(e);
}

// super.add()
public boolean add(E e) {
	// 调用offer()方法,如果成功返回true,如果失败抛出异常
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}

public boolean offer(E e) {
	// 存入元素不可为空
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
    	// 如果数组满了就返回false
        if (count == items.length)
            return false;
        // 如果数组没满就调用入队方法并返回true
        else {
            enqueue(e);
            return true;
        }
    } finally {
    	// 解锁
        lock.unlock();
    }
}

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;
    // 数量加1
    count++;
    // 唤醒notEmpty(因为入队了一个元素,所以肯定不为空了)
    notEmpty.signal();
}

public void put(E e) throws InterruptedException {
	// 存入元素不可为空
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 加锁,如果线程中断了抛出异常
    lock.lockInterruptibly();
    try {
    	// 如果数组满了,使用notFull等待(这里之所以使用while而不是if,是考虑到多线程并发问题)
        while (count == items.length)
            notFull.await();
        // 入队
        enqueue(e);
    } finally {
    	// 解锁
        lock.unlock();
    }
}

public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
	// 存入元素不可为空
    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 加锁,如果线程中断了抛出异常
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
        	// 如果超时就返回false
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        // 入队
        enqueue(e);
        return true;
    } finally {
    	// 解锁
        lock.unlock();
    }
}

      从上面诸多方法可以看出,存入队列的数据不能为空。每个方法对应的作用如下:
        ●add(E e):将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回true。如果此队列已满,则抛出IllegalStateException异常。
        ●offer(E e):将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回true。如果此队列已满,则返回false。
        ●put(E e):将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间(阻塞)。
        ●offer(E e, long timeout, TimeUnit unit):将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。超时则返回false。
      虽然方法比较简单易懂,但是细节之处无不体现出严谨与巧妙。

    3、取数据方法

public E poll() {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
    	// 如果队列没有元素则返回null,否则出队
        return (count == 0) ? null : dequeue();
    } finally {
    	// 解锁
        lock.unlock();
    }
}

private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    // 获取取指针位置的元素
    E x = (E) items[takeIndex];
    // 把取指针位置设为null
    items[takeIndex] = null;
    // 取指针前移,如果到头了就返回数组前端循环利用
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 元素数量减1
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // 唤醒notFull(因为取出了一个元素,所以肯定不满了)
    notFull.signal();
    return x;
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 加锁,如果线程中断了抛出异常
    lock.lockInterruptibly();
    try {
    	// 如果队列无元素,则阻塞等待nanos纳秒
        while (count == 0) {
        	// 如果超时就返回null
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        // 出队
        return dequeue();
    } finally {
    	// 解锁
        lock.unlock();
    }
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 加锁,如果线程中断了抛出异常
    lock.lockInterruptibly();
    try {
    	// 如果队列无元素,则阻塞等待在条件notEmpty上
        while (count == 0)
            notEmpty.await();
        // 出队
        return dequeue();
    } finally {
    	// 解锁
        lock.unlock();
    }
}

public boolean remove(Object o) {
    if (o == null) return false;
    final Object[] items = this.items;
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
    	// 如果队列有数据,则遍历寻找对应的数据删除
        if (count > 0) {
            final int putIndex = this.putIndex;
            int i = takeIndex;
            do {
                if (o.equals(items[i])) {
                    removeAt(i);
                    return true;
                }
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);
        }
        return false;
    } finally {
    	// 解锁
        lock.unlock();
    }
}

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

  ④总结

    ●ArrayBlockingQueue不需要扩容,因为是初始化时指定容量,并循环利用数组。
    ●ArrayBlockingQueue利用takeIndex和putIndex循环利用数组。
    ●入队和出队各定义了四种方法满足不同的用途。
    ●利用ReentrantLock和两个Condition保证并发安全。


三、:PriorityBlockingQueue

  ①PriorityBlockingQueue简介

    一般队列都是采用FIFO原则来确定线程执行的先后顺序。而PriorityBlockingQueue是一个支持优先级无界阻塞队列。默认情况下元素采用自然顺序升序排序,当然我们也可以通过构造函数来指定Comparator来对元素进行排序。需要注意的是PriorityBlockingQueue不能保证同优先级元素的顺序。
    PriorityBlockingQueue底层是基于数组实现的结构。

  ②PriorityBlockingQueue的关键属性

// 默认容量
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;

// 扩容的时候使用的控制变量,CAS更新这个值,谁更新成功了谁扩容,其它线程让出CPU
private transient volatile int allocationSpinLock;

// 不阻塞的优先级队列,非存储元素的地方,仅用于序列化/反序列化时
private PriorityQueue<E> q;

    根据属性可以看出,也是利用ReentrantLock来保证并发安全。相比上面的ArrayBlockingQueue,少了notFull属性,也说明PriorityBlockingQueue是无界队列。

  ③PriorityBlockingQueue的重点方法分析

    1、构造方法

// 默认容量为11
public PriorityBlockingQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}

// 指定容量
public PriorityBlockingQueue(int initialCapacity) {
    this(initialCapacity, null);
}

// 指定容量,指定比较器
public PriorityBlockingQueue(int initialCapacity,
                             Comparator<? super E> comparator) {
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    // 创建锁
    this.lock = new ReentrantLock();
    // 初始化Condition
    this.notEmpty = lock.newCondition();
    this.comparator = comparator;
    // 初始化二叉堆数组
    this.queue = new Object[initialCapacity];
}

// 使用指定集合内容
public PriorityBlockingQueue(Collection<? extends E> c) {
    this.lock = new ReentrantLock();
    this.notEmpty = lock.newCondition();
    boolean heapify = true; // true if not known to be in heap order
    boolean screen = true;  // true if must screen for nulls
    if (c instanceof SortedSet<?>) {
        SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
        this.comparator = (Comparator<? super E>) ss.comparator();
        heapify = false;
    }
    else if (c instanceof PriorityBlockingQueue<?>) {
        PriorityBlockingQueue<? extends E> pq =
            (PriorityBlockingQueue<? extends E>) c;
        this.comparator = (Comparator<? super E>) pq.comparator();
        screen = false;
        if (pq.getClass() == PriorityBlockingQueue.class) // exact match
            heapify = false;
    }
    Object[] a = c.toArray();
    int n = a.length;
    // If c.toArray incorrectly doesn't return Object[], copy it.
    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();
}

    2、存数据方法

public boolean add(E e) {
    return offer(e);
}

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
        notEmpty.signal();
    } finally {
    	// 解锁
        lock.unlock();
    }
    return true;
}

      从上面的方法可以看出,存入队列的数据不能为空。内部方法执行流程如下:
        ●加锁。
        ●判断是否需要扩容。
        ●添加元素并做自下而上的堆化。
        ●元素个数加1并唤醒notEmpty,唤醒取元素的线程。
        ●解锁。

      ⅰ、tryGrow()
private void tryGrow(Object[] array, int oldCap) {
	// 解锁(外部offer()方法加的锁),通过allocationSpinLock变量控制扩容的过程,防止阻塞的线程过多
    lock.unlock(); // must release and then re-acquire main lock
    Object[] newArray = null;
    // CAS更新allocationSpinLock变量为1的线程获得扩容资格
    if (allocationSpinLock == 0 &&
        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                 0, 1)) {
        try {
        	// 旧容量小于64则翻倍,旧容量大于64则增加一半
            int newCap = oldCap + ((oldCap < 64) ?
                                   (oldCap + 2) : // grow faster if small
                                   (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;
        }
    }
    // 未执行上面的扩容操作进入这里
    if (newArray == null) // back off if another thread is allocating
    	// 让不执行扩容的线程让出CPU
        Thread.yield();
    // 再次加锁
    lock.lock();
    // 当新数组创建成功并且旧数组没有被替换过
    if (newArray != null && queue == array) {
    	// 队列赋值为新数组
        queue = newArray;
        // 拷贝旧数组元素到新数组中
        System.arraycopy(array, 0, newArray, 0, oldCap);
    }
}

        代码主要流程如下:
          ●解锁(解除offer()方法中加的锁)。
          ●使用allocationSpinLock变量的CAS操作来控制扩容的过程。
          ●旧容量小于64则翻倍,旧容量大于64则增加一半。
          ●创建新数组。
          ●allocationSpinLock的值置0,相当于解锁控制扩容。
          ●其它线程在扩容的过程中要让出CPU。
          ●再次加锁。
          ●新数组创建成功,把旧数组元素拷贝过来,并返回到offer()方法中继续添加元素操作。

      ⅱ、siftUpComparable()
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];
        // 如果key大于父节点,堆化结束
        if (key.compareTo((T) e) >= 0)
            break;
        // 否则,交换二者的位置,继续下一轮比较
        array[k] = e;
        k = parent;
    }
    // 找到了应该放的位置,放入元素
    array[k] = key;
}

        使用默认的比较器执行插入元素操作。(自下而上堆化)

      ⅲ、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;
}

        使用指定的比较器执行插入元素操作。(自下而上堆化)

    3、取数据方法

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

private E dequeue() {
	// 元素个数减1
    int n = size - 1;
    // 数组无元素,返回null
    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
        size = n;
        return result;
    }
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    E result;
    try {
        while ((result = dequeue()) == null && nanos > 0)
            nanos = notEmpty.awaitNanos(nanos);
    } finally {
        lock.unlock();
    }
    return result;
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lockInterruptibly();
    E result;
    try {
    	// 如果队列没有元素,就阻塞在notEmpty上;如果出队成功,就跳出这个循环
        while ((result = dequeue()) == null)
            notEmpty.await();
    } finally {
    	// 解锁
        lock.unlock();
    }
    return result;
}

      以take()方法为例,内部方法执行流程如下:
        ●加锁。
        ●判断是否出队成功,未成功就阻塞在notEmpty上。
        ●出队时弹出堆顶元素,并对剩余元素做自上而下的堆化。
        ●解锁。

      ⅰ、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;           // loop while a non-leaf
        while (k < half) {
        	// 待调整位置左节点位置
            int child = (k << 1) + 1; // assume left child is least
            // 左节点
            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使用ReentrantLock和notEmpty的Condition控制并发安全。
    ●PriorityBlockingQueue扩容时使用一个单独变量的CAS操作来控制只有一个线程进行扩容。
    ●入队使用自下而上的堆化。
    ●出队使用自上而下的堆化。


四、:DelayQueue

  ①DelayQueue简介

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

    DelayQueue主要用于两个方面:
      ●清除缓存中超时的数据。
      ●任务超时处理。

  ②DelayQueue的关键属性

// 锁
private final transient ReentrantLock lock = new ReentrantLock();

// 优先级队列(根据Delay时间排序)
private final PriorityQueue<E> q = new PriorityQueue<E>();

// 取元素时,用于标记当前是否有线程在排队(优化阻塞)
private Thread leader = null;

// Condition(用于表示现在是否有可取的元素)
private final Condition available = lock.newCondition();

    根据属性参数,可以猜测DelayQueue的内部实现机制为:以支持优先级无界队列的PriorityQueue作为一个容器,容器里面的元素都应该实现Delayed接口,在每次往优先级队列中添加元素时以元素的过期时间作为排序条件,最先过期的元素放在优先级最高。

  ③DelayQueue的重点方法分析

    1、构造方法

public DelayQueue() {}

public DelayQueue(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;
}

    2、存数据方法

public void put(E e) {
    offer(e);
}

public boolean add(E e) {
    return offer(e);
}

public boolean offer(E e, long timeout, TimeUnit unit) {
    return 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();
    }
}

      入队方法执行流程如下:
        ●加锁。
        ●添加元素到PriorityQueue中。
        ●如果添加的元素是队首元素,就把leader置为空,并唤醒等待在条件available上的线程。
        ●解锁。

      入队方法比较简单,但是在判断当前元素是否为对首元素,如果是的话则把leader置为空,这是性能优化的一个步骤,代表取操作未被线程占用。

    3、取数据方法

      ⅰ、poll()
public E poll() {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        E first = q.peek();
        // 队首元素如果为空或者还未到期,就返回null;否则出队返回对应元素
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
            return q.poll();
    } finally {
    	// 解锁
        lock.unlock();
    }
}

        方法执行流程如下:
          ●加锁。
          ●检查队首元素,如果为空或者还没到期,就返回null。
          ●如果队首元素到期了,就调用PriorityQueue的poll()方法取出第一个元素。
          ●解锁。

      ⅱ、take()
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);
                // 如果已到期,出队返回对应元素
                if (delay <= 0)
                    return q.poll();
                // help GC,防止内存泄漏
                first = null; // don't retain ref while waiting
                // 如果有其他线程在操作,阻塞等待
                if (leader != null)
                    available.await();
                else {
                	// 将当前线程设置为leader(独占)
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                    	// 超时阻塞(到delay时间自动唤醒)
                        available.awaitNanos(delay);
                    } finally {
                    	// 释放leader
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
    	// 成功出队后,如果leader为空且队中还有元素,就唤醒下一个等待的线程
        if (leader == null && q.peek() != null)
        	// signal()只是把等待的线程放到AQS的队列里面,并不是真正的唤醒
            available.signal();
        // 解锁
        lock.unlock();
    }
}

        方法执行流程如下:
          ●加锁。
          ●判断队首元素是否为空,为空的话直接阻塞等待。
          ●判断队首元素是否到期,到期了直接调用PriorityQueue的poll()方法取出元素。
          ●如果队首元素不为空且没到期,再判断是否有其它线程占用资源,有则直接等待。
          ●如果没有其它线程占用资源,则把自己当作第一个线程,等待delay时间后唤醒,再尝试获取元素。
          ●获取到元素之后再唤醒下一个等待的线程。
          ●解锁。

        关于设置first = null防止内存泄漏的解释:
          假如有线程A进入,设置leader = 线程A。此时线程B也来了,因为leader != null,则会阻塞。线程C、D、E……同理。假如线程A成功将队首元素出列,之后队首元素应该会被回收掉。但是它还被线程B、线程C等等持有着,所以不会被回收。如果线程无限多,就会造成内存泄漏。

  ④总结

    ●DelayQueue是阻塞队列,内部存储结构使用PriorityQueue。
    ●DelayQueue使用ReentrantLockCondition来控制并发安全。
    ●DelayQueue常用于定时任务。


五、:SynchronousQueue

  ①SynchronousQueue简介

    SynchronousQueue作为BlockingQueue大家庭中的一员,与其他BlockingQueue有着与众不同的特性:
      ●SynchronousQueue没有容量。SynchronousQueue是一个不存储元素的BlockingQueue。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
      ●因为没有容量,所以对应peek()、contains()、clear()、isEmpty()等方法其实是无效的。例如:clear是不执行任何操作的,contains始终返回false,peek始终返回null。
      ●SynchronousQueue分为公平和非公平,默认情况下采用非公平访问策略。
      ●若使用TransferQueue,则队列中永远会存在一个dummy node。

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

  ②SynchronousQueue的关键属性及类

    1、关键属性

// CPU数量
static final int NCPUS = Runtime.getRuntime().availableProcessors();

// 有超时的情况下自旋次数(当CPU数量小于2时不自旋)
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;

// 没有超时的情况下自旋次数
static final int maxUntimedSpins = maxTimedSpins * 16;

// 针对有超时的情况,自旋了多次后,如果剩余时间大于1000纳秒就使用带时间的LockSupport.parkNanos()方法
static final long spinForTimeoutThreshold = 1000L;

// 传输器(即两个线程交换元素使用的东西)
private transient volatile Transferer<E> transferer;

      通过观察属性,我们可以发现以下内容:
        ●这个阻塞队列里面是会有自旋操作的。
        ●内部使用了一个叫做transferer的东西来交换元素。

    2、关键类

// Transferer抽象类,包含一个transfer()方法用来传输元素
abstract static class Transferer<E> {
    abstract E transfer(E e, boolean timed, long nanos);
}

// 以栈方式实现的Transferer
static final class TransferStack<E> extends Transferer<E> {
	// 栈中节点的几种类型:
    // 消费者(请求数据的)
    static final int REQUEST    = 0;
    // 生产者(提供数据的)
    static final int DATA       = 1;
    // 二者正在匹配中
    static final int FULFILLING = 2;

    /** Node class for TransferStacks. */
    static final class SNode {
    	// 下一个节点
        volatile SNode next;        // next node in stack
        // 匹配者
        volatile SNode match;       // the node matched to this
        // 等待着的线程
        volatile Thread waiter;     // to control park/unpark
        // 元素
        Object item;                // data; or null for REQUESTs
        // 节点类型(对应上面的)
        int mode;
    }
	// 栈的头节点
    volatile SNode head;
}

// 以队列方式实现的Transferer
static final class TransferQueue<E> extends Transferer<E> {
    // 队列中的节点
    static final class QNode {
    	// 下一个节点
        volatile QNode next;          // next node in queue
        // 元素
        volatile Object item;         // CAS'ed to or from null
        // 等待着的线程
        volatile Thread waiter;       // to control park/unpark
        // 是否是数据节点
        final boolean isData;
    }

    // 队列的头节点
    transient volatile QNode head;
    // 队列的尾节点
    transient volatile QNode tail;
}

      通过观察类,我们可以发现以下内容:
        ●定义了一个抽象类Transferer,内含一个传输元素的方法。
        ●有两种实现类,一种是栈(LIFO),一种是队列(FIFO)。
        ●栈只需要保存一个头节点(存取元素都是操作头节点)。
        ●队列需要保存一个头节点和一个尾节点(元素操作尾节点元素操作头节点)。
        ●每个节点中都保存着存储的元素、等待着的线程,以及下一个节点。

  ③SynchronousQueue的重点方法分析

    1、构造方法

public SynchronousQueue() {
    this(false);
}

public SynchronousQueue(boolean fair) {
    transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}

      通过构造方法,我们可以看到前面有栈和队列两种实现的原因:结构的结果是实现非公平非公平模式,队列结构的结果是实现公平模式。

    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();
    }
}

      从方法中可以直观的看到,存入的数据不能为空。

    3、取数据方法

public E take() throws InterruptedException {
    E e = transferer.transfer(null, false, 0);
    // 如果取到了元素就返回
    if (e != null)
        return e;
    // 如果未取到元素,让线程中断并抛出中断异常
    Thread.interrupted();
    throw new InterruptedException();
}

    4、TransferStack的transfer()方法

      从上面可以看出,存取数据时都调用了transfer()方法,因此核心逻辑在这里体现。以TransferStack中的transfer()方法为例:

// 入参依次是:传输的元素,是否需要超时,超时的时间
E transfer(E e, boolean timed, long nanos) {
	SNode s = null; // constructed/reused as needed
	// 根据e是否为null判断是生产者(存操作)还是消费者(取操作)
	int mode = (e == null) ? REQUEST : DATA;
	
	for (;;) {
		// 获取栈顶
	    SNode h = head;
	    // 栈顶为空,或者栈顶模式跟当前模式一致
	    if (h == null || h.mode == mode) {  // empty or same-mode
	    	// 如果有超时而且已到期
	        if (timed && nanos <= 0) {      // can't wait
	        	// 如果栈顶不为空且是取消状态,弹出栈顶(进入下一次循环)
	            if (h != null && h.isCancelled())
	                casHead(h, h.next);     // pop cancelled node
	            // 如果栈顶为空或不是取消状态,直接返回null
	            else
	                return null;
	        // 尝试入栈成功
	        } else if (casHead(h, s = snode(s, e, h, mode))) {
	        	// 自旋+阻塞当前入栈的线程,并等待被匹配到
	            SNode m = awaitFulfill(s, timed, nanos);
	            // 如果m等于s,说明取消了,那么就把它清除掉,并返回null
	            if (m == s) {               // wait was cancelled
	                clean(s);
	                return null;
	            }
	            
	            // 到这里说明匹配到元素了(经过awaitFulfill()方法,要么被取消了,要么匹配)
	            
				// 如果栈顶不为空,并且栈顶的下一个节点是s,就把栈顶换成s的下一个节点(弹出h和s)
	            if ((h = head) != null && h.next == s)
	                casHead(h, s.next);     // help s's fulfiller
	            // 根据当前节点的模式判断返回m还是s中的值
	            return (E) ((mode == REQUEST) ? m.item : s.item);
	        }
	        
	    // 到这里说明栈顶模式和当前模式不一样
	    
	    // 如果栈顶不是正在匹配中
	    } else if (!isFulfilling(h.mode)) { // try to fulfill
	    	// 如果栈顶已经取消了,就把它弹出
	        if (h.isCancelled())            // already cancelled
	            casHead(h, h.next);         // pop and retry
	        // 栈顶未取消,就让当前节点先入栈,再让他们尝试匹配(s成为了新的栈顶,它的状态是FULFILLING)
	        else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
	            for (;;) { // loop until matched or waiters disappear
	                SNode m = s.next;       // m is s's match
	                // 如果m为null(除s节点外的节点都被其它线程抢先匹配了),就清空栈并跳出内部循环,到外部循环再重新入栈判断
	                if (m == null) {        // all waiters are gone
	                    casHead(s, null);   // pop fulfill node
	                    s = null;           // use new node next time
	                    break;              // restart main loop
	                }
	                SNode mn = m.next;
	                // 如果m和s尝试匹配成功,就弹出栈顶的两个元素m和s
	                if (m.tryMatch(s)) {
	                    casHead(s, mn);     // pop both s and m
	                    return (E) ((mode == REQUEST) ? m.item : s.item);
	                // 尝试匹配失败(m已经被其它线程抢先匹配了),协助清除它
	                } else                  // lost match
	                    s.casNext(m, mn);   // help unlink
	            }
	        }
	        
	    // 栈顶模式匹配中
	    } else {                            // help a fulfiller
	        SNode m = h.next;               // m is h's match
	        // 如果m为null(m已经被其它线程抢先匹配了),弹出已匹配的节点
	        if (m == null)                  // waiter is gone
	            casHead(h, null);           // pop fulfilling node
	        else {
	            SNode mn = m.next;
	            // 如果m和h尝试匹配成功,就弹出栈顶的两个元素m和h
	            if (m.tryMatch(h))          // help match
	                casHead(h, mn);         // pop both h and m
	            // 尝试匹配失败(m已经被其它线程抢先匹配了),协助清除它
	            else                        // lost match
	                h.casNext(m, mn);       // help unlink
	        }
	    }
	}
}

      代码整体量比较多,逻辑也不少,其主要执行流程是:
        ●如果栈中没有元素,或者栈顶模式跟将要入栈的元素模式一样,就入栈。
        ●入栈后自旋等待一会看有没有其它线程匹配到它,自旋完了还没匹配到元素就阻塞等待。
        ●阻塞等待被唤醒了说明其它线程匹配到了当前元素,就返回匹配到的元素。
        ●如果两者模式不一样,且栈顶未在匹配中,就拿当前节点尝试匹配,匹配成功了就返回匹配到的元素。
        ●如果两者模式不一样,且栈顶正在匹配中,当前线程就协助去匹配,匹配完成了再让当前节点重新入栈重新匹配。

      ⅰ、awaitFulfill()
// 入参依次是:需要等待的节点,是否需要超时,超时的时间
SNode awaitFulfill(SNode s, boolean timed, long nanos) {
	// 获取到期时间
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    Thread w = Thread.currentThread();
    // 自旋次数
    int spins = (shouldSpin(s) ?
                 (timed ? maxTimedSpins : maxUntimedSpins) : 0);
    for (;;) {
    	// 当前线程中断了,尝试清除s
        if (w.isInterrupted())
            s.tryCancel();
        // 检查s是否匹配到了元素m(也有可能是其它线程的m匹配到当前线程的s)
        SNode m = s.match;
        // 如果匹配到了,直接返回m
        if (m != null)
            return m;
        // 如果需要超时,判断超时时间
        if (timed) {
            nanos = deadline - System.nanoTime();
            // 如果已超时,尝试清除s
            if (nanos <= 0L) {
                s.tryCancel();
                continue;
            }
        }
        // 如果还有自旋次数,自旋次数减一,并进入下一次自旋
        if (spins > 0)
            spins = shouldSpin(s) ? (spins-1) : 0;
        // 后面都是自旋次数没有了的情况
        // 如果s的waiter为null,把当前线程注入进去,并进入下一次自旋
        else if (s.waiter == null)
            s.waiter = w; // establish waiter so can park next iter
        // 如果不允许超时,直接阻塞,并等待被其它线程唤醒,唤醒后继续自旋并查看是否匹配到了元素
        else if (!timed)
            LockSupport.park(this);
        // 如果允许超时且还有剩余时间,就阻塞相应时间
        else if (nanos > spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanos);
    }
}
      ⅱ、tryMatch()
// 从上面代码看到,是m调用的tryMatch()方法
boolean tryMatch(SNode s) {
	// 如果m还没有匹配者,就把s作为它的匹配者
    if (match == null &&
        UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
        Thread w = waiter;
        // 唤醒m中的线程,两者匹配完毕
        if (w != null) {    // waiters need at most one unpark
            waiter = null;
            LockSupport.unpark(w);
        }
        return true;
    }
    // 也可能其它线程先一步匹配了m,返回最终匹配到的是否是s
    return match == s;
}

    5、TransferQueue的transfer()方法

      看完TransferStack中的transfer()方法,其实TransferQueue中的transfer()方法思想一致:

E transfer(E e, boolean timed, long nanos) {
    QNode s = null; // constructed/reused as needed
    // 当前节点是否是数据节点(模式)
    boolean isData = (e != null);

    for (;;) {
        QNode t = tail;
        QNode h = head;
        // 头/尾节点为null(没有初始化),自旋等待
        if (t == null || h == null)         // saw uninitialized value
            continue;                       // spin

		// 头尾节点相等(队列为null)或者当前节点和队列节点模式一样
        if (h == t || t.isData == isData) { // empty or same-mode
            QNode tn = t.next;
            // t != tail(已有其他线程操作了,修改了tail),重新循环
            if (t != tail)                  // inconsistent read
                continue;
            // tn != null(已经有其他线程添加了节点),tn推进,重新循环
            if (tn != null) {               // lagging tail
            	// 当前线程帮忙推进尾节点(尝试将tn设置为尾节点)
                advanceTail(t, tn);
                continue;
            }
            // 需要限时并且已经超时,直接返回null
            if (timed && nanos <= 0)        // can't wait
                return null;
            // 如果s == null,构建一个新节点Node
            if (s == null)
                s = new QNode(e, isData);
            // 将新建的节点加入到队列中,如果不成功,重新循环
            if (!t.casNext(null, s))        // failed to link in
                continue;

			// 替换尾节点
            advanceTail(t, s);              // swing tail and wait
            // 自旋+阻塞当前线程,并等待被匹配到
            Object x = awaitFulfill(s, e, timed, nanos);
            // x == s(当前线程已经超时或者中断)
            if (x == s) {                   // wait was cancelled
            	// 清理节点s
                clean(t, s);
                return null;
            }

			// 判断节点是否已经从队列中离开
            if (!s.isOffList()) {           // not already unlinked
            	// 尝试将s节点设置为head,移出t
                advanceHead(t, s);          // unlink if head
                if (x != null)              // and forget fields
                    s.item = s;
                // 释放等待线程
                s.waiter = null;
            }
            return (x != null) ? (E)x : e;

		// 队列不为null 且 当前节点和队列节点模式不一样
        } else {                            // complementary-mode
            QNode m = h.next;               // node to fulfill
            // 不一致读(有其他线程做了更改)
            if (t != tail || m == null || h != head)
                continue;                   // inconsistent read

			// 生产者producer和消费者consumer匹配
            Object x = m.item;
            // isData == (x != null)(isData与x的模式相同,表示已经匹配了)
            if (isData == (x != null) ||    // m already fulfilled
            	// x == m(m节点被取消了)
                x == m ||                   // m cancelled
                // !m.casItem(x, e)(尝试将数据e设置到m上失败)
                !m.casItem(x, e)) {         // lost CAS
                // 将m设置为头结点,h出队,重新循环
                advanceHead(h, m);          // dequeue and retry
                continue;
            }

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

      代码相比TransferStack中的transfer()方法较为简单,其主要执行流程是:
        ●如果队列为null或者尾节点模式与当前节点模式一致,则尝试将节点加入到等待队列中(采用自旋的方式),直到被匹配、超时或者被取消。匹配成功的话要么返回null(生产者返回的),要么返回真正传递的值(消费者返回的),如果返回的是node节点本身则表示当前线程超时或者被取消了。
        ●如果队列不为null,且队列的节点是当前节点匹配的节点,则进行数据的传递匹配并返回匹配节点的数据。
        ●整个过程中都会检测并帮助其他线程推进。

      ⅰ、awaitFulfill()
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.tryCancel(e);
        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);
    }
}

  ④总结

    ●SynchronousQueue是无缓冲队列,用于在两个线程之间直接移交元素。
    ●SynchronousQueue有两种实现方式,一种是公平方式(队列),一种是非公平方式()。
    ●栈方式中的节点有三种模式:生产者、消费者、正在匹配中。
    ●栈方式的大致思路是:如果栈顶模式跟当前节点模式一致就入栈并等待被匹配,否则就匹配,匹配到了就返回。
    ●队列方式中的节点模式分为:数据节点和非数据节点。
    ●队列方式的大致思路是:如果队列为null或者尾节点模式与当前节点模式一致就入队并等待被匹配,否则就匹配,匹配到了就返回。

    SynchronousQueue是严格意义上的无缓冲队列吗?

      通过源码分析,我们发现其实在SynchronousQueue内部,或使用栈或使用队列来存储包含线程和元素值的节点,如果同一模式的节点过多的话,它们都会存储进来,且都会阻塞着,所以,严格上来说,SynchronousQueue并不能算是一个无缓冲队列。

      同时,引出一个SynchronousQueue的缺点:
        如果生产者速度经常大于消费者速度,生产者则易发生阻塞。因此,SynchronousQueue一般用于生产、消费的速度大致相当的情况,这样才不会导致系统中过多的线程处于阻塞状态。


六、:LinkedTransferQueue

  ①LinkedTransferQueue简介

    大多数BlockingQueue对读或者写都是锁上整个队列,在并发量大的时候并不适用,而上面的SynchronousQueue虽然不会锁住整个队列,但它又没有容量。那么有没有既有容量又不会锁住整个队列的实现呢?这里就引出了LinkedTransferQueue。
    LinkedTransferQueue是基于链表的FIFO无界阻塞队列。Doug Lea大神说LinkedTransferQueue是一个聪明的队列。它是ConcurrentLinkedQueue、SynchronousQueue(公平模式)、无界的LinkedBlockingQueues等的超集。

    LinkedTransferQueue采用一种预占模式。即消费者线程到队列中取元素时,如果发现队列为空,则会生成一个null节点,然后调用park()方法等待生产者。后面如果生产者线程入队时发现有一个null元素节点,这时生产者就不会入队了,直接将元素填充到该节点上,唤醒该节点的消费者线程,被唤醒的消费者线程取到元素返回。

  ②LinkedTransferQueue的关键属性及类

    1、关键属性

// 是否为多核
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()方法操作参数
private static final int NOW   = 0;
private static final int ASYNC = 1;
private static final int SYNC  = 2;
private static final int TIMED = 3;

    2、关键类

static final class Node {
	// 节点是否为数据节点(区分是存数据还是取数据)
    final boolean isData;   // false if this is a request node
    // 存放的数据
    volatile Object item;   // initially non-null if isData; CASed to match
    // 指向下一个节点
    volatile Node next;
    // 调用了park()方法的线程
    volatile Thread waiter; // null until waiting
}

      通过isData的值,区分是存操作(isData为false)还是取操作(isData为true)。

  ③LinkedTransferQueue的重点方法分析

    1、构造方法

public LinkedTransferQueue() {
}

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

      通过构造方法也可以看出,没有设置初始容量的变量,因此是无界队列。

    2、存数据方法

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

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

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

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

      存数据的几个方法实现都是一样的,使用ASYNC的方式调用xfer()方法,传入的参数都一模一样。(设置timeout的地方是什么鬼?)

    3、取数据方法

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();
}

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

      这里也是调用xfer()方法,但跟存数据不同的是,各自传入的操作参数不同,poll()方法传入的是NOW和TIMED,take()方法传入的是SYNC。

    4、xfer()方法

      由上面可以看出,无论存取都是调用的xfer()方法,可见此方法为核心方法。

// 入参依次是:操作元素,是否是数据节点,上面提到的操作参数,超时时间
private E xfer(E e, boolean haveData, int how, long nanos) {
	// 存入元素不可为空
    if (haveData && (e == null))
        throw new NullPointerException();
    Node s = null;                        // the node to append, if needed

    retry:
    for (;;) {                            // restart on append race
		// 从头节点开始尝试匹配,直到匹配成功或者到尾节点
        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的内容设置为e(如果是取元素则e是null,如果是存元素则e是元素值)
                // 如果匹配成功
                if (p.casItem(item, e)) { // match
                	// 由于控制多线程同时存取元素时出现竞争的情况
                    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
                        // 如果新的头节点为空,或者其next为空,或者其next不匹配,跳出循环重试
                        if ((h = head)   == null ||
                            (q = h.next) == null || !q.isMatched())
                            break;        // unless slack < 2
                    }
                    // 唤醒p中等待的线程
                    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
        if (how != NOW) {                 // No matches available
        	// 新建s节点
            if (s == null)
                s = new Node(e, haveData);
            // 尝试入队
            Node pred = tryAppend(s, haveData);
            // 入队失败,重试
            if (pred == null)
                continue retry;           // lost race vs opposite mode
            // 如果模式不为ASYNC,就等待被匹配
            if (how != ASYNC)
                return awaitMatch(s, pred, e, (how == TIMED), nanos);
        }
        return e; // not waiting
    }
}

      作为核心逻辑处理方法,代码还是比较复杂的,其主要执行流程是:
        ●先查看队列的头节点,是否与传入元素的模式一致。
        ●如果模式不一样,就尝试让他们匹配。如果头节点被别的线程先匹配走了,就尝试与头节点的下一个节点匹配,如此一直往后,直到匹配成功或到达链表尾为止。
        ●如果模式一样,或者到达链表尾,就尝试入队。
        ●入队的时候有可能链表尾已经被修改了,那就尾指针后移,再重新尝试入队,依此往复。
        ●如果入队成功了,就自旋或阻塞。如果阻塞了就等待被其它线程匹配到并唤醒。
        ●被唤醒之后进入下一次循环就匹配到元素了,返回匹配到的元素。
        ●是否需要入队及阻塞,根据操作参数不同,有四种情况:

      ⅰ、tryAppend()
private Node tryAppend(Node s, boolean haveData) {
	// 从尾节点开始遍历
    for (Node t = tail, p = t;;) {        // move p to last node and append
        Node n, u;                        // temps for reads of next & tail
        // 如果首尾都是null(链表中还没有元素)
        if (p == null && (p = head) == null) {
        	// 就让首节点指向s
            if (casHead(null, s))
                return s;                 // initialize
        }
        // 如果p无法处理,则返回null(p和s节点的类型不同,不允许s入队)
        else if (p.cannotPrecede(haveData))
            return null;                  // lost race vs opposite mode
        // 如果p的next不为空(不是最后一个节点),则让p重新指向最后一个节点
        else if ((n = p.next) != null)    // not last; keep traversing
            p = p != t && t != (u = tail) ? (t = u) : // stale tail
                (p != n) ? n : null;      // restart if off list
        // 如果CAS更新s为p的next失败(有其它线程先一步更新到p的next了),让p指向p的next,重新尝试让s入队
        else if (!p.casNext(null, s))
            p = p.next;                   // re-read on CAS failure
        // 到这里说明s成功入队了
        else {
        	// 如果p不等于t,就更新尾节点
            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);
            }
            // 返回p,即s的前一个元素
            return p;
        }
    }
}
      ⅱ、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
    // 随机数,随机让一些自旋的线程让出CPU
    ThreadLocalRandom randomYields = null; // bound if needed

    for (;;) {
        Object item = s.item;
        // 如果s元素的值不等于e(匹配成功)
        if (item != e) {                  // matched
            // assert item != s;
            // 把s的item更新为s本身,并把s中的waiter置为空
            s.forgetContents();           // avoid garbage
            // 返回匹配到的元素
            return LinkedTransferQueue.<E>cast(item);
        }
        // 如果当前线程中断了或者有超时的到期了,就更新s的元素值指向s本身
        if ((w.isInterrupted() || (timed && nanos <= 0)) &&
                s.casItem(e, s)) {        // cancel
            // 尝试解除s与其前一个节点的关系(删除s节点)
            unsplice(pred, s);
            // 返回元素的值本身,说明没匹配到
            return e;
        }

		// 如果自旋次数小于0,就计算自旋次数
        if (spins < 0) {                  // establish spins at/near front
        	// 如果前面有节点未被匹配就返回0,如果前面有节点且正在匹配中就返回一定的次数,自旋等待
            if ((spins = spinsFor(pred, s.isData)) > 0)
            	// 初始化随机数
                randomYields = ThreadLocalRandom.current();
        }
        else if (spins > 0) {             // spin
        	// 自旋次数自减
            --spins;
            // 随机让出CPU
            if (randomYields.nextInt(CHAINED_SPINS) == 0)
                Thread.yield();           // occasionally yield
        }
        else if (s.waiter == null) {
        	// 更新s的waiter为当前线程
            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);
        }
    }
}

  ④总结

    ●LinkedTransferQueue可以看作LinkedBlockingQueue、SynchronousQueue(公平模式)、ConcurrentLinkedQueue三者的集合体。
    ●LinkedTransferQueue使用双重队列的数据结构实现。
    ●不论存取元素都会入队
    ●先尝试跟头节点比较,如果二者模式不一样,就匹配它们,匹配成功返回对方的值。
    ●如果二者模式一样,就入队,并自旋或阻塞等待被唤醒。
    ●有四种操作模式:NOW、ASYNC、SYNC、TIMED。
    ●LinkedTransferQueue全程都没有使用synchronized、ReentrantLock等比较重的锁,基本是通过 自旋+CAS 实现。
    ●入队之后,先自旋一定次数后再调用LockSupport.park()或LockSupport.parkNanos阻塞。


七、:LinkedBlockingDeque

  ①LinkedBlockingDeque简介

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

  ②LinkedBlockingDeque的关键属性及类

    1、关键属性

// 头节点
transient Node<E> first;

// 尾节点
transient Node<E> last;

// 表中节点个数
private transient int count;

// 容量(创建时指定)
private final int capacity;

// 锁
final ReentrantLock lock = new ReentrantLock();

// 非空条件
private final Condition notEmpty = lock.newCondition();

// 非满条件
private final Condition notFull = lock.newCondition();

    通过观察属性,我们可以看到几个重要信息:
      ●利用ReentrantLock来保证并发安全。
      ●利用Condition作为多线程中消息通知机制。(当获取数据的消费者线程被阻塞时会将该线程放置到notEmpty等待队列中,当存入数据的生产者线程被阻塞时,会将该线程放置到notFull等待队列中)

    2、关键类

static final class Node<E> {
	// 节点中的元素
    E item;

	// 前驱节点
    Node<E> prev;

	// 后继节点
    Node<E> next;
}

  ③LinkedBlockingDeque的重点方法分析

    1、构造方法

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

public LinkedBlockingDeque(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
}

public LinkedBlockingDeque(Collection<? extends E> c) {
    this(Integer.MAX_VALUE);
    final ReentrantLock lock = this.lock;
    lock.lock(); // Never contended, but necessary for visibility
    try {
        for (E e : c) {
            if (e == null)
                throw new NullPointerException();
            if (!linkLast(new Node<E>(e)))
                throw new IllegalStateException("Deque full");
        }
    } finally {
        lock.unlock();
    }
}

      从构造方法可以直观看到,默认容量为Integer.MAX_VALUE。

    2、存数据方法

      ⅰ、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 {
    	// 如果插入失败,使用notFull等待
        while (!linkFirst(node))
            notFull.await();
    } finally {
    	// 解锁
        lock.unlock();
    }
}

private boolean linkFirst(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    // 判断是否超出容量
    if (count >= capacity)
        return false;
    Node<E> f = first;
    // 新节点的next指向头节点
    node.next = f;
    // 设置node为新的头节点
    first = node;
    // 如果没有尾节点,设置node为尾节点
    if (last == null)
        last = node;
    // 如果有尾节点,那就将之前头节点的前驱节点设置为node
    else
        f.prev = node;
    ++count;
    // 唤醒notEmpty
    notEmpty.signal();
    return true;
}

        代码逻辑比较简单,就是将指定的元素插入双端队列的头,如果失败(空间占满)将一直等待可用空间。

      ⅱ、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;
}

        代码逻辑与上面类似,只是插入到双端队列的尾。

    3、取数据方法

      ⅰ、pollFirst()
public E pollFirst() {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        return unlinkFirst();
    } finally {
    	// 解锁
        lock.unlock();
    }
}

private E unlinkFirst() {
    // assert lock.isHeldByCurrentThread();
    Node<E> f = first;
    // 如果头节点为空,直接返回null
    if (f == null)
        return null;
    Node<E> n = f.next;
    E item = f.item;
    // 移除头节点
    f.item = null;
    f.next = f; // help GC
    first = n;
    // 移除后为空队列,尾节点设为null
    if (n == null)
        last = null;
    // 移除后不为空队列,n的前驱节点设为null
    else
        n.prev = null;
    --count;
    notFull.signal();
    return item;
}

        代码逻辑比较简单,就是获取并移除此双端队列的头节点。如果双端队列为空,返回null。

      ⅱ、pollLast()
public E pollLast() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return unlinkLast();
    } 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;
}

        代码逻辑与上面类似,只是获取并移除的是双端队列的尾节点。

  ④总结

    ●LinkedBlockingDeque基于链表,使用双端队列思想实现。
    ●LinkedBlockingDeque是有界队列,不传入容量时默认为Integer.MAX_VALUE。


八、:LinkedBlockingQueue

  ①LinkedBlockingQueue简介

    LinkedBlockingQueue是用链表实现的有界阻塞队列,队列默认大小为Integer.MAX_VALUE。

  ②LinkedBlockingQueue的关键属性及类

    1、关键属性

// 容量(创建时指定)
private final int capacity;

// 表中节点个数
private final AtomicInteger count = new AtomicInteger();

// 头节点
transient Node<E> head;

// 尾节点
private transient Node<E> last;

// 取锁
private final ReentrantLock takeLock = new ReentrantLock();

// 非空条件
private final Condition notEmpty = takeLock.newCondition();

// 存锁
private final ReentrantLock putLock = new ReentrantLock();

// 非满条件
private final Condition notFull = putLock.newCondition();

    通过观察属性,我们可以看到几个重要信息:
      ●利用ReentrantLock来保证并发安全。存取使用了不同的锁,锁分离提高了效率。
      ●利用Condition作为多线程中消息通知机制。(当获取数据的消费者线程被阻塞时会将该线程放置到notEmpty等待队列中,当存入数据的生产者线程被阻塞时,会将该线程放置到notFull等待队列中)

    2、关键类

static class Node<E> {
	// 节点中的元素
    E item;

	// 后继节点
    Node<E> next;
}

  ③LinkedBlockingQueue的重点方法分析

    1、构造方法

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

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

public LinkedBlockingQueue(Collection<? extends E> c) {
    this(Integer.MAX_VALUE);
    final ReentrantLock putLock = this.putLock;
    putLock.lock(); // Never contended, but necessary for visibility
    try {
        int n = 0;
        for (E e : c) {
            if (e == null)
                throw new NullPointerException();
            if (n == capacity)
                throw new IllegalStateException("Queue full");
            enqueue(new Node<E>(e));
            ++n;
        }
        count.set(n);
    } finally {
        putLock.unlock();
    }
}

      从构造方法可以直观看到,默认容量为Integer.MAX_VALUE。

    2、存数据方法

public void put(E e) throws InterruptedException {
	// 存入元素不可为空
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    // 加存锁
    putLock.lockInterruptibly();
    try {
        // 如果队列满了,使用notFull等待
        while (count.get() == capacity) {
            notFull.await();
        }
        // 如果队列不满,执行入队
        enqueue(node);
        c = count.getAndIncrement();
        // 如果当前队列长度小于容量,唤醒notFull(因为使用锁分离而多出的操作)
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
    	// 解存锁
        putLock.unlock();
    }
    // 如果原队列长度为0,现在存入了一个元素,唤醒notEmpty
    if (c == 0)
        signalNotEmpty();
}

private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    // 将元素加在尾节点后面
    last = last.next = node;
}

private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    // 加取锁
    takeLock.lock();
    try {
    	// 唤醒notEmpty
        notEmpty.signal();
    } finally {
    	// 解取锁
        takeLock.unlock();
    }
}

      从代码可以看出,存入队列的数据不能为空。整体逻辑比较简单:
        ●加存锁。
        ●如果队列满了,使用notFull等待。否则入队。
        ●如果入队后元素数量小于容量,唤醒其它阻塞在notFull上的线程。
        ●解存锁。
        ●如果存入元素前队列中元素数量为0,唤醒notEmpty。

    3、取数据方法

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    // 加取锁
    takeLock.lockInterruptibly();
    try {
    	// 如果队列无元素,使用notEmpty等待
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 如果队列有元素,执行出队
        x = dequeue();
        c = count.getAndDecrement();
        // 如果取之前队列长度大于1,唤醒notEmpty
        if (c > 1)
            notEmpty.signal();
    } finally {
    	// 解取锁
        takeLock.unlock();
    }
    // 如果取之前队列长度等于容量,现在取出了一个元素,唤醒notFull
    if (c == capacity)
        signalNotFull();
    return x;
}

private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    // 头节点本身不存储任何元素,把头节点后继节点清空设置为头节点,返回其原来内容
    Node<E> h = head;
    Node<E> first = h.next;
    h.next = h; // help GC
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}

private void signalNotFull() {
    final ReentrantLock putLock = this.putLock;
    // 加存锁
    putLock.lock();
    try {
    	// 唤醒notFull
        notFull.signal();
    } finally {
    	// 解存锁
        putLock.unlock();
    }
}

      代码整体逻辑比较简单:
        ●加取锁。
        ●如果队列无元素,使用notEmpty等待。否则出队。
        ●如果出队前元素数量大于1,唤醒其它阻塞在notEmpty上的线程。
        ●解取锁。
        ●如果取出元素之前队列长度等于容量,唤醒notFull。

  ④总结

    ●LinkedBlockingQueue采用单链表的形式实现。
    ●LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞。
    ●LinkedBlockingQueue是有界队列,不传入容量时默认为Integer.MAX_VALUE。

系列文章传送门:

JUC探险-1、初识概貌
JUC探险-2、synchronized
JUC探险-3、volatile
JUC探险-4、final
JUC探险-5、原子类
JUC探险-6、Lock & AQS
JUC探险-7、ReentrantLock
JUC探险-8、ReentrantReadWriteLock
JUC探险-9、Condition
JUC探险-10、常见工具、数据结构
JUC探险-11、ConcurrentHashMap
JUC探险-12、CopyOnWriteArrayList
JUC探险-13、ConcurrentLinkedQueue
JUC探险-14、ConcurrentSkipListMap
JUC探险-15、BlockingQueue
JUC探险-16、ThreadLocal
JUC探险-17、线程池

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值