目录

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详解以及对比

ArrayBlockingQueue LinkedBlockingQueue PriorityBlockingQueue SynchronousQueue
数据结构 数组 单向链表 数组(二叉堆) 单向链表
怎么实现阻塞 Lock+Condition Lock+Condition Lock+Condition CAS+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. 参考

Java源码分析系列笔记-12.BlockingQueue的更多相关文章

  1. Java源码分析系列之HttpServletRequest源码分析

    从源码当中 我们可以 得知,HttpServletRequest其实 实际上 并 不是一个类,它只是一个标准,一个 接口而已,它的 父类是ServletRequest. 认证方式 public int ...

  2. Java源码分析系列

    1) 深入Java集合学习系列:HashMap的实现原理 2) 深入Java集合学习系列:LinkedHashMap的实现原理 3) 深入Java集合学习系列:HashSet的实现原理 4) 深入Ja ...

  3. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  4. Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析

    Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析 前言 上一篇文章Spring Ioc源码分析系列--Ioc源码入口分析已经介绍到Ioc容器 ...

  5. Spring Ioc源码分析系列--容器实例化Bean的四种方法

    Spring Ioc源码分析系列--实例化Bean的几种方法 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到bean真正通过那些方式实例化出来的时候,并没有继续分 ...

  6. Spring mvc源码分析系列--Servlet的前世今生

    Spring mvc源码分析系列--Servlet的前世今生 概述 上一篇文章Spring mvc源码分析系列--前言挖了坑,但是由于最近需求繁忙,一直没有时间填坑.今天暂且来填一个小坑,这篇文章我们 ...

  7. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  8. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  9. MyCat源码分析系列之——前后端验证

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...

  10. jquery2源码分析系列

    学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中 ...

随机推荐

  1. Chrome 133 版本开发者工具(DevTools)更新内容

    Chrome 133 版本开发者工具(DevTools)更新内容 一.持久化的 AI 聊天记录 AI 助手面板会在本地持久化聊天记录,即使重新加载 DevTools 或 Chrome,也可以查看之前与 ...

  2. BUUCTF---RSA1

    RSA基础概念 rsa原理: RSA公开密钥密码体制的原理是:根据数论,寻求两个大素数比较简单,而将它们的乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥 RSA算法的具体描述如下: (1) ...

  3. [源码系列:手写spring] IOC第十四节:容器事件和事件监听器

    代码分支 https://github.com/yihuiaa/little-spring/tree/event-and-event-listenerhttps://github.com/yihuia ...

  4. 面试题-MyBatis框架

    前言 MyBatis框架部分的题目,是我根据Java Guide的面试突击版本V3.0再整理出来的,其中,我选择了一些比较重要的问题,并重新做出相应回答,并添加了一些比较重要的问题,希望对大家起到一定 ...

  5. exGCD 2025/1/10

    前言: 全文均为蒟蒻作者手搓 虽也参考了许多 [luogu 题解/某SDN/cnblogs.com] 但很多地方均为作者瞎编 如有错误务必救救孩子 File int GCD(int a,int b){ ...

  6. SQL Server 查看版本信息

    SQL Server 查看版本信息3种方法: 1) 使用命令行查看 [Win + R]键 -> 打开cmd 2) 使用SSMS查看 打开并连接SSMS后查看 3) 通过服务器属性查看 使用SSM ...

  7. Windows 7 下右键发送到菜单项没了

    问题描述: 突然有一天,Windows 7 下右键发送到菜单项没了,如图所示: 问题原因 黑人问号脸?

  8. 字符串匹配究极大招【KMP】:带你一步步从原理到构建

    目录 前言 KMP原理 什么是前缀表 如何构建前缀表 next数组 使用next数组做匹配 实战演练 前言 一文带你了解如何去理解并实现KMP算法.本文用于记录自己的学习过程,同时向大家进行分享相关的 ...

  9. MySQL 中的 Log Buffer 是什么?它有什么作用?

    MySQL 中的 Log Buffer 是什么?它有什么作用? Log Buffer 是 MySQL InnoDB 存储引擎的一部分,用于存储写入日志数据的内存区域.它主要用于记录事务的变更日志,这些 ...

  10. python 3 No module named ‘Crypto‘ 解决方案

    pip3 install pycryptodome pip3 install crypto Pip3 install pycrypto 本机(mac)环境的解决方案: pip3 uninstall p ...