15. Java JUC源码分析系列笔记-BlockingQueue

本文隶属于Java JUC源码分析系列专栏

文章目录

1. 是什么

线程安全的阻塞队列。
特点:

  • 先进先出:
    既然是队列那肯定是先进先出
  • 阻塞
    支持在插入元素时,如果队列已满,那么阻塞,等待队列非满
    也支持在删除元素时,如果队列为空,那么阻塞,等待队列非空
  • 无界有界
    数组容量的大小。无界其实是Integer.MAX_VALUE
  • 线程安全

2. 使用场景

生产者、消费者

3. 如何使用

方法\处理方式抛出异常返回特殊值一直阻塞超时退出
插入方法add(e)offer(e)put(e)offer(e,time,unit)
移除方法remove()poll()take()poll(time,unit)
检查方法element()peek()不可用不可用

4. 各种BlockingQueue详解以及对比

ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueueSynchronousQueue
数据结构数组单向链表数组(二叉堆)单向链表
怎么实现阻塞Lock+ConditionLock+ConditionLock+ConditionCAS+LockSupport
有界/无界有界有界无界无界(不存储元素)
吞吐量(以LinkedBlockingQueue为基准)比LinkedBlockingQueue低(读读、读写、写写相互阻塞)/ (读读、写写相互阻塞,读写不相互阻塞)无界(读读、读写、写写相互阻塞)比LinkedBlockingQueue高(读写匹配才能进行下去)

5. ArrayBlockingQueue

5.1. 是什么

使用Object数组实现的有界的阻塞队列
读读、读写、写写相互阻塞

5.2. 如何使用

public class ArrayBlockingQueueTest
{
    public static void main(String[] args) throws InterruptedException
    {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        CountDownLatch latch = new CountDownLatch(2);

        new Thread(()->{
            for (int i = 0;;i++)
            {
                try
                {
                 	String data = "data" + i;
                    queue.put(data);
                    System.out.println("Producer放入消息:" + data);
                    TimeUnit.SECONDS.sleep(1);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        new Thread(()->{
            for (;;)
            {
                try
                {
                    System.out.println("Consumer获取消息:" + queue.take());
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        latch.await();

    }
}

5.2.1. 方法选择

方法\处理方式抛出异常返回特殊值一直阻塞超时退出
插入方法add(e)offer(e)put(e)offer(e,time,unit)
移除方法remove()poll()take()poll(time,unit)
检查方法element()peek()不可用不可用

5.3. 原理分析

5.3.1. uml

5.3.2. 构造方法

5.3.2.1. 底层使用数组+Lock+Condtion实现
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
	//底层是数组实现的
	final Object[] items;

	//take, poll, peek or remove等读方法,读取下一个元素的位置
	int takeIndex;
	//put, offer, or add等方法,写入下一个元素的位置
	int putIndex;

	//数组中实际元素的数量
	//当count==item.length()的时候说明数组已满
	int count;

	//一个锁说明读写互斥
	final ReentrantLock lock;
    //两个条件量
    private final Condition notEmpty;//用来唤醒读线程
	private final Condition notFull;//用来唤醒写线程

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

5.3.3. 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();
    }
}
  • 4行:加锁。一旦该写线程加锁其他读写线程都不能同时进来
  • 8-9行:如果数组已经满了,那么阻塞等待
  • 11行:没满则入队并唤醒读者

下面具体分析:

5.3.3.1. 加锁
//加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
    //...
} finally {
    lock.unlock();
}
5.3.3.2. 如果数组已经满了,那么等待
//如果数组已经满了,那么等待。直到读者取出元素后唤醒
while (count == items.length)
    notFull.await();
5.3.3.3. 没满则入队并唤醒读者
enqueue(e);
  • enqueue
private void enqueue(E x) {
    //把元素加入到队尾
    final Object[] items = this.items;
    items[putIndex] = x;
    //已插入到末尾,重置插入索引为0
    //这个数组是可以循环使用的,不需要扩容。
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    //插入后唤醒读者
    notEmpty.signal();
}

5.3.4. take【阻塞】

public E take() throws InterruptedException {
	//加锁
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
    	//如果数组为空,那么等待。写者加入元素后唤醒
        while (count == 0)
            notEmpty.await();
    	//出队
        return dequeue();
    } finally {
    	//释放锁
        lock.unlock();
    }
}
  • 3行:加锁。一旦该读线程加锁其他读写线程都不能同时进来
  • 6-8行:如果数组为空,那么等待
  • 10行:不为空则出队并唤醒写者

下面具体分析:

5.3.4.1. 加锁
//加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
 try {
	//...
} finally {
	//释放锁
    lock.unlock();
}

5.3.4.2. 如果数组为空,那么等待
//如果数组为空,那么等待。写者加入元素后唤醒
while (count == 0)
    notEmpty.await();
5.3.4.3. 不为空则出队并唤醒写者
  • dequeue
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    //获取最后一个元素并置为null
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    //已取到末尾,重置取值索引为0
     //这个数组是可以循环使用的,不需要扩容。
    if (++takeIndex == items.length)
        takeIndex = 0;
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    //出队后唤醒写者
    notFull.signal();
    return x;
}

5.3.5. offer【返回特殊值】

public boolean offer(E e) {
    checkNotNull(e);
    //加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	//已满,直接返回false
        if (count == items.length)
            return false;
        else {
        	//未满,加入队列同时唤醒读者
            enqueue(e);
            return true;
        }
    } finally {
    	//解锁
        lock.unlock();
    }
}

5.3.6. poll【返回特殊值】

public E poll() {
    final ReentrantLock lock = this.lock;
	//加锁
    lock.lock();
    try {
    	//长度为0直接返回null,否则出队并唤醒写者
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

5.3.7. add【抛出异常】

public boolean add(E e) {
	//简单调用AbstractQueue的add方法
    return super.add(e);
}

//AbstractQueue的add方法
public boolean add(E e) {
	//调用ArrayBlockingQueue的方offer法
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}


5.3.8. remove【抛出异常】

public E remove() {
	//简单调用poll方法
    E x = poll();
    if (x != null)
        return x;
    else
    	//没有元素,抛出异常
        throw new NoSuchElementException();
}

5.3.9. element【抛出异常】

public E element() {
	//调用peek方法
    E x = peek();
    if (x != null)
        return x;
    else
    	//为空直接抛出异常
        throw new NoSuchElementException();
}

5.3.10. peek【返回特殊值】

public E peek() {
	//加锁
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		return itemAt(takeIndex); // null when queue is empty
	} finally {
		//解锁
		lock.unlock();
	}
}
	
	
@SuppressWarnings("unchecked")
final E itemAt(int i) {
	//直接返回数组中的第i个元素
    return (E) items[i];
}

5.4. 总结

底层使用数组实现,是个有界队列。
并且用了一个锁和两个condition。一个锁说明读写互斥,两个conditon说明读写相互唤醒

6. LinkedBlockingQueue

6.1. 是什么

使用单向链表实现的有界的阻塞队列
读读、写写相互阻塞,读写不相互阻塞
吞吐量比ArrayBlockingQueue高

6.2. 如何使用

public class LinkedBlockingQueueTest
{
    public static void main(String[] args) throws InterruptedException
    {
        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(1);
        CountDownLatch latch = new CountDownLatch(2);

        new Thread(()->{
            for (int i = 0;;i++)
            {
                try
                {
                 	String data = "data" + i;
                    queue.put(data);
                    System.out.println("Producer放入消息:" + data);
                    TimeUnit.SECONDS.sleep(1);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        new Thread(()->{
            for (;;)
            {
                try
                {
                    System.out.println("Consumer获取消息:" + queue.take());
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        latch.await();

    }
}

6.3. 源码分析

6.3.1. 构造方法

6.3.1.1. 底层使用单向链表+Lock+Condition实现
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

  	//最大长度
    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();

    //如果读操作的时候队列是空的,那么等待 notEmpty 条件
    private final Condition notEmpty = takeLock.newCondition();

    //入队时用的锁。锁住队尾
    private final ReentrantLock putLock = new ReentrantLock();

    // 如果写操作的时候队列是满的,那么等待 notFull 条件
    private final Condition notFull = putLock.newCondition();

	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);//头节点是个占位符
	}
}
6.3.1.2. Node
static class Node<E> {
    E item;

    //单向队列
    Node<E> next;

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

结构如下图:

6.3.2. put【阻塞】

public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    //加写锁
    putLock.lockInterruptibly();
    try {
        //链表实际容量到达链表最大容量,阻塞等待读者取出
        while (count.get() == capacity) {
            notFull.await();
        }
    	//加入尾部
        enqueue(node);
        c = count.getAndIncrement();//+1,不过返回的是c的原值
        if (c + 1 < capacity)
            notFull.signal();//唤醒其他写者?
    } finally {
        putLock.unlock();
    }
    //c == 0 说明原来queue是空的, 那么可能有其他读线程阻塞住了。
    if (c == 0)
    	//所以这里 唤醒正在 poll/take 等待中的线程
        signalNotEmpty();
}
  • 8行:加写锁。一旦加了写锁其他写者无法同时进来写入数据,但是读者可以同时进来读
  • 11-13行:链表实际容量到达链表最大容量,那么写者阻塞等待读者取出
  • 15行:链表没有满的话,那么把该元素添加至尾部
  • 16行:更新队列中元素的数量,+1,返回原值
  • 17-18行:添加了元素后发现队列还是没有满,那么唤醒其他写者继续添加
  • 23-25行:由这句c = count.getAndIncrement();可看出+1后返回的是c的原值,如果为0说明之前队列可能为空,那么加读锁、唤醒读者读取元素、解读锁

下面具体分析:

6.3.2.1. 加写锁
//加写锁
putLock.lockInterruptibly();
try {
//...
} finally {
    putLock.unlock();
}
6.3.2.2. 如果队列已满那么等待
//链表实际容量到达链表最大容量,阻塞等待读者取出
while (count.get() == capacity) {
    notFull.await();
}
6.3.2.3. 未满则入队
  • enqueue
private void enqueue(Node<E> node) {
    //把节点加入到链表尾部,并且更新last指针
    last = last.next = node;
}
6.3.2.4. 入队完发现队列没满,那么继续唤醒写者入队
if (c + 1 < capacity)
    notFull.signal();//唤醒其他写者
6.3.2.5. 入队完解锁后发现之前队列是空的,那么唤醒读者
//c == 0 说明原来queue是空的, 那么可能有其他读线程阻塞住了。
if (c == 0)
	//所以这里 唤醒正在 poll/take 等待中的线程
    signalNotEmpty();
  • signalNotEmpty
 private void signalNotEmpty() {
 	//加读锁
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
    	//唤醒读者
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

6.3.3. take【阻塞】

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
	//加了读锁
    takeLock.lockInterruptibly();
    try {
    	//长度为0,阻塞等待写着加入
        while (count.get() == 0) {
            notEmpty.await();
        }
    	//删除第一个节点
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();//唤醒其他读者?
    } finally {
        takeLock.unlock();
    }
    //c == capacity 说明原来queue是满的, 那么可能有其他写线程阻塞住了。
    if (c == capacity)
    	//所以这里 唤醒正在 put 等待中的线程
        signalNotFull();
    return x;
}
  • 7行:加读锁。一旦加了读锁其他读者无法同时进来读取数据,但是写者可以同时进来写数据
  • 10-12行:链表实际容量为0,那么读者阻塞等待写者写入
  • 14行:链表不为空的话,那么删除链表头部的元素
  • 15行:更新队列中元素的数量,-1,返回原值
  • 16-17行:取出了元素后发现队列还是不为空,那么唤醒其他读者继续读取
  • 22-24行:由这句c = count.getAndDecrement();可看出-1后返回的是c的原值,当他为capacity的时候说明之前队列可能是满的,那么加写锁、唤醒写者写入元素、解写锁

下面具体分析:

6.3.3.1. 加读锁
//加了读锁
takeLock.lockInterruptibly();
try {
    //....
} finally {
    takeLock.unlock();
}
6.3.3.2. 队列为空那么等待
//长度为0,阻塞等待写着加入
while (count.get() == 0) {
    notEmpty.await();
}
6.3.3.3. 未空则出队
  • dequeue
private E dequeue() {
    Node<E> h = head;//头节点是个占位符
    Node<E> first = h.next;//真正的第一个节点
    h.next = h; // help GC 头节点next指向头节点自己?
    head = first;//更新头节点指向第一个节点(即从队头出队)
    E x = first.item;
    first.item = null;
    return x;
}
6.3.3.4. 出了队发现队列没空,那么继续唤醒读者
if (c > 1)
    notEmpty.signal();//唤醒其他读者?
6.3.3.5. 出了队解了锁发现之前队列是满的,那么唤醒写者
if (c == capacity)
	//加写锁,唤醒写者
    signalNotFull();
  • signalNotFull
private void signalNotFull() {
	final ReentrantLock putLock = this.putLock;
	//加写锁
	putLock.lock();
	try {
		//通知写者没满,可以写了
		notFull.signal();
	} finally {
		putLock.unlock();
	}
}

6.3.4. offer 返回特殊值

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
	if (count.get() == capacity)
		return false;
	int c = -1;
	Node<E> node = new Node<E>(e);
	final ReentrantLock putLock = this.putLock;
	putLock.lock();
	try {
		if (count.get() < capacity) {
			enqueue(node);
			c = count.getAndIncrement();
			if (c + 1 < capacity)
				notFull.signal();
		}
	} finally {
		putLock.unlock();
	}
	if (c == 0)
		signalNotEmpty();
	return c >= 0;//跟put不同的地方在这里,返回而不阻塞
}

6.3.5. poll 返回特殊值

public E poll() {
    final AtomicInteger count = this.count;
    if (count.get() == 0)//跟take不同的地方在这里,返回null
        return null;
    E x = null;
    int c = -1;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        if (count.get() > 0) {
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

6.3.6. peek 返回特殊值

public E peek() {
    if (count.get() == 0)//为空返回null
        return null;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        Node<E> first = head.next;
        if (first == null)
            return null;
        else
            return first.item;
    } finally {
        takeLock.unlock();
    }
    //不需要唤醒写着,因为没有出队
}

6.4. 总结

底层使用单向数组实现,可以有界也可以无界队列。
并且用了两个锁和两个condition。两个个锁说明读写可以同时进行,两个conditon说明读写相互唤醒

7. PriorityBlockingQueue

7.1. 是什么

底层使用数组(二叉堆)实现的无界的阻塞队列
读读、读写、写写相互阻塞
可以排序
由于无界,所以put操作不会阻塞,但是take操作会阻塞(队列为空的时候)

7.1.1. 二叉堆

一颗完全二叉树,堆序性质为,每个节点的值都小于其左右子节点的值,二叉堆中最小的值就是根节点。
底层用数组进行存储。对于数组中的元素 a[i],其左子节点为 a[2i+1],其右子节点为 a[2i + 2],其父节点为 a[(i-1)/2]。
结构如下图:

7.2. 如何使用

public class PriorityBlockingQueueTest
{
    public static void main(String[] args) throws InterruptedException
    {
        PriorityBlockingQueue<String> queue = new PriorityBlockingQueue<>(1);
        CountDownLatch latch = new CountDownLatch(2);

        new Thread(()->{
            for (int i = 0;;i++)
            {
                try
                {
                    String data = "data" + i;
                    queue.put(data);
                    System.out.println("Producer放入消息:" + data);
                    TimeUnit.SECONDS.sleep(1);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        new Thread(()->{
            for (;;)
            {
                try
                {
                    System.out.println("Consumer获取消息:" + queue.take());
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        latch.await();

    }
}

7.3. 原理分析

7.3.1. 构造方法

7.3.1.1. 底层使用数组+Lock+Condition实现
public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {


	//底层使用数组实现(堆)
	private transient Object[] queue;
	//实际使用的长度
    private transient int size;

    //comparator确定元素的顺序,如果是null那么是自然序
    private transient Comparator<? super E> comparator;

    //只有一把锁说明读写互斥
    private final ReentrantLock lock;

	//只有一个condition说明只有读或者写的操作是阻塞的
    //当队列不为空的时候唤醒读操作
    private final Condition notEmpty;

	// 这个也是用于锁,用于数组扩容的时候,需要先获取到这个锁,才能进行扩容操作
	// 其使用 CAS 操作
	private transient volatile int allocationSpinLock;

	public PriorityBlockingQueue() {
		//默认11个,自然序
        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();
		this.notEmpty = lock.newCondition();
		this.comparator = comparator;
		this.queue = new Object[initialCapacity];
	}
}

7.3.2. put

public void put(E e) {
    //转调offer
    offer(e); // never need to block
}
7.3.2.1. 转调offer,不需要阻塞
  • offer
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;
		//自然序。把e加入到数组array末尾的位置n,然后与父亲比较,若是比父亲小则交换位置
        if (cmp == null)
            siftUpComparable(n, e, array);
        else
            siftUpUsingComparator(n, e, array, cmp);
        size = n + 1;
        //唤醒读者
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
    return true;
}
  • 6行:加锁。一旦该写者加了锁,那么其他读写线程不能进来操作
  • 10-11行:根据需要进行扩容
  • 13-18行:插入数组末尾,并且通过上浮操作保持堆的性质
  • 19行:队列中元素的实际数量+1
  • 21行:其他读者可能在队列为空的时候阻塞,这里需要唤醒
    由上面的代码可以看出写的时候是不需要阻塞的,因为这个队列是无界的
7.3.2.1.1. 加锁
final ReentrantLock lock = this.lock;
    //加锁
    lock.lock();
} finally {
    lock.unlock();
}
7.3.2.1.2. 判断是否需要扩容
//如果当前队列中的元素个数 >= 数组的大小,那么需要扩容了
    while ((n = size) >= (cap = (array = queue).length))
        tryGrow(array, cap);
7.3.2.1.2.1. 需要的话进行扩容
  • tryGrow
private void tryGrow(Object[] array, int oldCap) {
	//为什么这里释放锁?让读的线程可以读而不至于再扩容的时候阻塞
    lock.unlock(); // must release and then re-acquire main lock
    Object[] newArray = null;
    //allocationSpinLock为0表示没有其他进行扩容,1表示有
    //当没有其他线程扩容 且 当前线程CAS加锁成功才进行扩容
    if (allocationSpinLock == 0 &&
        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                 0, 1)) {
        try {
        	//如果旧容量<64,那么新容量=2*旧容量+2
        	//否则为1.5*旧容量
            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;
            }
        	//确实有扩容 且 array没有变动--说明没有其他线程在扩容?
            if (newCap > oldCap && queue == array)
                newArray = new Object[newCap];
        } finally {
        	//释放锁
            allocationSpinLock = 0;
        }
    }
    //其他线程在扩容,让出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);
    }
}
7.3.2.1.3. 把元素加入堆的末尾
//自然序。把e加入到数组array末尾的位置n,然后与父亲比较,若是比父亲小则交换位置
if (cmp == null)
    siftUpComparable(n, e, array);
7.3.2.1.3.1. 上浮操作调整堆
  • siftUpComparable
//把x,插入到堆数组array,的k位置
private static <T> void siftUpComparable(int k, T x, Object[] array) {
    Comparable<? super T> key = (Comparable<? super T>) x;
	//最多调整到root即0
    while (k > 0) {
    	//父节点的位置 (k-1)/2
        int parent = (k - 1) >>> 1;
        Object e = array[parent];
        
        //如果x比父节点大,那么退出
        if (key.compareTo((T) e) >= 0)
            break;

        //否则与父节点交换位置
        array[k] = e;
        //从父节点继续往上
        k = parent;
    }
    //走到这里说明k位置存放x满足二叉堆的性质:比父节点大,比左右孩子小
    array[k] = key;
}
7.3.2.1.3.2. 调整的过程图

7.3.3. take

 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;
}
  • 4行:加锁。一旦该读者加了锁,那么其他读写线程不能进来操作
  • 8-9行:出队,如果队列为空那么进行阻塞,等待队列不为空的时候由写者唤醒
7.3.3.1. 加锁
 final ReentrantLock lock = this.lock;
    //加锁
    lock.lockInterruptibly();
    try {
    	//...
    } finally {
    	//解锁
        lock.unlock();
    }
7.3.3.2. 一直阻塞等待,直到出队成功
//出队元素为空那么阻塞等待唤醒
while ( (result = dequeue()) == null)
    notEmpty.await();
7.3.3.2.1. 出队具体操作
  • dequeue
private E dequeue() {
	//队列为空返回null
    int n = size - 1;
    if (n < 0)
        return null;
    else {
        Object[] array = queue;
        //root节点,即0号位置就是出队的元素
        E result = (E) array[0];
       
        E x = (E) array[n];//数组末尾的元素x
        array[n] = null;
        Comparator<? super E> cmp = comparator;
        if (cmp == null)
         	//把数组末尾的元素x放到0号位置,调整堆
            siftDownComparable(0, x, array, n);
        else
            siftDownUsingComparator(0, x, array, n, cmp);
        size = n;
        return result;
    }
}

  • 7-12行:移除堆顶,末尾元素放到堆顶
  • 14-18行:下沉操作调整堆
7.3.3.2.1.1. 移除堆顶,末尾元素放到堆顶
Object[] array = queue;
//root节点,即0号位置就是出队的元素
E result = (E) array[0];

E x = (E) array[n];//数组末尾的元素x
array[n] = null;
7.3.3.2.1.2. 下沉操作调整堆
  • siftDownComparable
//把元素x,插入到长度为n,的堆数组array的,k位置
private static <T> void siftDownComparable(int k, T x, Object[] array,
                                           int n) {
    if (n > 0) {
        Comparable<? super T> key = (Comparable<? super T>)x;
        //只能在 非叶子节点(有孩子的节点)调整
        int half = n >>> 1;           
        while (k < half) {
        	//左孩子
            int child = (k << 1) + 1; // 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];
            //c是左右孩子中较小的那个
            //如果要插入的元素比左右孩子都小,那么二叉堆性质以满足,无需调整
            if (key.compareTo((T) c) <= 0)
                break;
            //否则将较小的孩子上移
            array[k] = c;
            //继续往下调整
            k = child;
        }
        //走到这里说明k位置存放x满足二叉堆的性质:比父节点大,比左右孩子小
        array[k] = key;
    }
}
7.3.3.2.1.3. 调整的过程图

7.4. 总结

无界队列,底层使用二叉堆实现,有序。
写不阻塞,读阻塞

8. SynchronousQueue

8.1. 是什么

底层使用单向实现的阻塞队列,不存储元素
一个写者必须同时有一个读者才能进行下去,反之亦然。
否则写者将会一直阻塞或者读者将会一直阻塞

8.2. 使用

public class SynchronousQueueTest
{
    public static void main(String[] args) throws InterruptedException
    {
        SynchronousQueue <String> queue = new SynchronousQueue<>();
        CountDownLatch latch = new CountDownLatch(2);

        new Thread(()->{
            for (int i = 0;;i++)
            {
                try
                {
                 	String data = "data" + i;
                    queue.put(data);
                    System.out.println("Producer放入消息:" + data);//不支持peek操作
                    TimeUnit.SECONDS.sleep(1);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        new Thread(()->{
            for (;;)
            {
                try
                {
                    System.out.println("Consumer获取消息:" + queue.take());
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    latch.countDown();
                }
            }
        }).start();

        latch.await();

    }
}

8.3. 原理

8.3.1. 构造方法

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

	private transient volatile Transferer<E> transferer;
    
	public SynchronousQueue() {
		this(false);//默认不公平,即用stack
	}

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

	//单向链表头、尾
    transient volatile QNode head;
    transient volatile QNode tail;
}
8.3.1.1. Transfer
abstract static class Transferer<E> {
     //put和take操作都会调用这个
     //如果e为空,那么代表读者的take操作
     //如果e不为空,那么代表写着的put操作
     // 第二个参数代表是否设置超时,如果设置超时,超时时间是第三个参数的值
    // 返回值如果是 null,代表超时,或者中断。具体是哪个,可以通过检测中断状态得到。
    abstract E transfer(E e, boolean timed, long nanos);
}
8.3.1.2. QNode
 static final class QNode {
    volatile QNode next;          // 单向链表
    volatile Object item;         // CAS'ed to or from null
    volatile Thread waiter;       // to control park/unpark
    final boolean isData;//true表示写,false表示读
}

8.3.2. put 阻塞

public void put(E e) throws InterruptedException {
	//写着e保证不为空
    if (e == null) throw new NullPointerException();
	//调用Transfer的transfer方法传递元素给读者
    if (transferer.transfer(e, false, 0) == null) {
        Thread.interrupted();
        throw new InterruptedException();
    }
}
8.3.2.1. 调用TransferQueue
  • TransferQueue transfer
E transfer(E e, boolean timed, long nanos) {
    QNode s = null;
    boolean isData = (e != null);//e不为空表示写(true),为空表示读(false)

    for (;;) {
        QNode t = tail;
        QNode h = head;
        if (t == null || h == null)         // saw uninitialized value
            continue;                       // spin

		//队列为空或者队列中尾节点的模式与当前节点一样(即都是写或者都是读的情况)
		//那么直接将当前节点入队
        if (h == t || t.isData == isData) {
            QNode tn = t.next;
            //之前的tail跟当前tail不同,说明已经有节点入队了,重新来一次
            if (t != tail)                 
                continue;
            //走到这里说明tail没有改变,可以tail.next居然不为空,说明有节点入队,但是还没有修改tail
            //那么把tail指向tail.next即可
            if (tn != null) {            
                advanceTail(t, tn);//tail==t的话,把tail指向tn
                continue;
            }
            //设置了超时但是时间不对
            if (timed && nanos <= 0)
                return null;
            //构造当前节点
            if (s == null)
                s = new QNode(e, isData);
            //插入到链表尾部
            if (!t.casNext(null, s))
                continue;

			//tail==t的话,把tail指向s
            advanceTail(t, s);              
            //自旋或者阻塞等待另一个模式的线程过来唤醒
            //写线程拿到的是null,读线程拿到的是写线程的值
            Object x = awaitFulfill(s, e, timed, nanos);

            //走到这里说明已经唤醒了,继续往下执行
            if (x == s) {                   // wait was cancelled
                clean(t, s);
                return null;
            }

			//当前节点的next不是当前节点
			//那么当头节点==尾节点的时候,CAS设置头为当前节点
            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 {                           
        	//头节点的next是当前节点
            QNode m = h.next;   
            //头节点或者尾节点或者头节点的next为空了,即链表改变了,重新开始            
            if (t != tail || m == null || h != head)
                continue;                   

            //失败重试的情况
            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;
            }

			//CAS需改头节点。如果h==head,那么修改头节点为当前节点
            advanceHead(h, m);              // successfully fulfilled
            //唤醒当前节点的线程。对应awaitFulfill
            LockSupport.unpark(m.waiter);
		
            return (x != null) ? (E)x : e;
        }
    }
}
  • advanceTail
void advanceTail(QNode t, QNode nt) {
	//如果当前尾节点==传过来的尾节点的话
    if (tail == t)
    	//CAS操作修改尾节点指针指向nt
        UNSAFE.compareAndSwapObject(this, tailOffset, t, nt);
}
  • awaitFulfill
//要么自旋、要么阻塞
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
    //设置了超时,那么计算超时到期的时间
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    Thread w = Thread.currentThread();
    //头节点的下一个节点就是我自己了,那么我不入队,而是自旋等待
    int spins = ((head.next == s) ?
                 (timed ? maxTimedSpins : maxUntimedSpins) : 0);
    for (;;) {
		//当前线程被中断了,那么将当前节点的item属性CAS设置为e
        if (w.isInterrupted())
            s.tryCancel(e);
        //这里是这个方法的唯一的出口
        //当前节点的item属性跟e不同的时候
        Object x = s.item;
        if (x != e)
            return x;
        //超时了,那么将当前节点的item属性CAS设置为e
        if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                s.tryCancel(e);
                continue;
            }
        }
    	//每次循环自旋-1
        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);
    }
}

  • tryCancel
void tryCancel(Object cmp) {
    UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
}
  • advanceHead
void advanceHead(QNode h, QNode nh) {
    if (h == head &&
        UNSAFE.compareAndSwapObject(this, headOffset, h, nh))
        h.next = h; // forget old next
}

8.3.3. take 阻塞

public E take() throws InterruptedException {
	//调用Transfer的transfer方法从写者获取元素
    E e = transferer.transfer(null, false, 0);
    if (e != null)
        return e;
    Thread.interrupted();
    throw new InterruptedException();
}

8.4. 总结

不存储元素,吞吐量比LinkedBlockingQueue高
读、写必须匹配才能进行下去,否则会加入队列阻塞等待,直到另一个模式的线程到来唤醒

9. 参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thinker QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值