目录
2.1.1 PriorityBlockingQueue序列化案例
2.3 PriorityBlockingQueue 堆化处理
2.4 PriorityBlockingQueue 新增业务
2.4.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止
2.4.4 第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
2.5 PriorityBlockingQueue 删除业务
2.5.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止
2.5.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
2.6 PriorityBlockingQueue 检查业务
2.7 PriorityBlockingQueue 扩容机制
2.8 java.lang.invoke.VarHandle
3.3.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止
3.3.4 第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
3.4.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止
3.4.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
3.7 LinkedBlockingQueue 相关阻塞业务
4.4.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止
4.4.4 第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
4.5.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止
4.5.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
一,BlockingQueue 接口
1.1 简介
上一章链接:java-01-基础篇-04 Java集合-03-Queue-CSDN博客
上一章小编讲解了Queue单向队列。这一章继续沿着单向队列讲解另一种队列。它就是阻塞队列BlockingQueue。
如下图所示,紫色是Queue队列单独一脉的实现接口,只支持单向队列功能,红色圈圈里面是单向阻塞队列(BlockingQueue);从继承图可以看出BlockingQueue 是Queue的子类。也可以得知该阻塞队列是单向的。Java里面提供两种阻塞队列,一种是单向的;一种的双向的;本次所讲解的是单向的阻塞队列(BlockingQueue);
1.2 BlockingQueue 特性
public interface BlockingQueue<E> extends Queue<E> {/*忽略代码*/}
从继承结构中可以得知,BlockingQueue是Queue的子类;
从官网得知。BlockingQueue不允许存储null值;还有一点就是在原本Queue接口之中,又额外追加的两组处理接口。
一组是Blocks(表示操作没有完成则一直阻塞下去;新增put(e),删除take() );
另一组是Time out(表示操作没有完成则一直阻塞,如果阻塞时间超过指定时间则会中断阻塞;新增offer(e, time, unit),删除poll(time, unit))。
package java.util.concurrent;
import java.util.Collection;
import java.util.Queue;
/**
* @since 1.5
* @author Doug Lea
* @param <E> the type of elements held in this queue
*/
public interface BlockingQueue<E> extends Queue<E> {
/** 新增元素,操作失败抛异常 */
boolean add(E e);
/** 新增元素,操作失败返回特殊值 */
boolean offer(E e);
/** 新增元素,操作发生阻塞则一直阻塞下去;直到新增操作成功为止*/
void put(E e) throws InterruptedException;
/** 新增元素,操作发生阻塞则一直阻塞下去,如果阻塞时长到指定的时间则进行阻塞中断*/
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
/** 删除元素,操作失败抛异常*/
boolean remove(Object o);
/** 删除/移除元素,操作发生阻塞则一直阻塞下去,直到删除操作成功为止 */
E take() throws InterruptedException;
/** 删除/移除元素,操作发生阻塞则一直阻塞下去,如果阻塞时长到指定的时间则进行阻塞中断 */
E poll(long timeout, TimeUnit unit) throws InterruptedException;
/** 阻塞队列剩余容量 */
int remainingCapacity();
/** 阻塞队列是否包含指定元素 */
boolean contains(Object o);
/** 此方法从队列中移除所有可用的元素,并将它们添加到指定的集合 c 中。如果队列为空,则该方法返回 0,否则返回移除的元素数量 */
int drainTo(Collection<? super E> c);
/** 这个方法和上面是一样的,只不过 额外提供一个移除数量的最大值;上面没有提供默认就是全部可用的元素*/
int drainTo(Collection<? super E> c, int maxElements);
}
所以BlockingQueue的新增,删除;分别有四种处理方法!
第一种【Throws exception】 | 操作失败抛异常 |
第二种【Special value】 | 操作失败返回特殊值(null/boolean) |
第三种【Blocks】 | 操作发生阻塞则一直阻塞下去,不会中断,直到操作成功为止 |
第四种【Times out】 | 操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁 |
1.3 测试drainTo方法案例
package com.toast.collection.queue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author toast
* @time 2024/5/8
* @remark
*/
public class TestBlockingQueue {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
// 向队列中添加一些元素
for (int i = 1; i <= 10; i++) {
queue.offer(i);
}
// 创建一个空的ArrayList集合
List<Integer> list = new ArrayList<>();
// 从队列中移除所有元素,并将它们转移到集合中
int removedCount = queue.drainTo(list);
// 打印移除的元素数量
System.out.println("移除的元素数量:" + removedCount);
// 集合的剩余容量: 默认值是Integer.MAX
System.out.println("集合的剩余容量:" + queue.remainingCapacity());
// 打印集合中的元素
System.out.println("集合中的元素:" + list);
}
}
如果是换一个方法,使用可以指定的移除最大数量的容量;案例如下:
package com.toast.collection.queue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author toast
* @time 2024/5/8
* @remark
*/
public class TestBlockingQueue {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
// 向队列中添加一些元素
for (int i = 1; i <= 10; i++) {
queue.offer(i);
}
// 创建一个空的ArrayList集合
List<Integer> list = new ArrayList<>();
// 从队列中移除所有元素,并将它们转移到集合中;移除最大数量指定为5
int removedCount = queue.drainTo(list, 5);
// 打印移除的元素数量
System.out.println("移除的元素数量:" + removedCount);
// 集合的剩余容量: 默认值是Integer.MAX
System.out.println("集合的剩余容量:" + queue.remainingCapacity());
// 打印集合中的元素
System.out.println("集合中的元素:" + list);
}
}
从BlockingQueue接口定义来看,在Queue标准业务接口下面,在将队列中的扩展业务就是处理阻塞的业务进行一个抽离;并形成一套标准的处理阻塞业务的队列接口BlockingQueue。
在Queue队列中的形成的标准阻塞队列接口,其对应也会为其提供基本的骨架也就是抽象类;具体的实现子类提供血与肉,从而形成一个整体;
不过在JAVA里面并没有提供一个叫做AbstractBlockQueue 的抽象类;也就是说阻塞队列的基本骨架还是用AbstractQueue 该抽象类,来提供队列的基本功能;而对于阻塞功能不同的子类使用不同的方式来实现;
二,PriorityBlockingQueue
java.util.concurrent.PriorityBlockingQueue 优先级阻塞队列;之前在讲解Queue也提供优先级队列PriorityQueue;都是优先级队列,一个是无阻塞处理,一个是有阻塞处理的优先级队列;
而且从PriorityBlockingQueue 的全限定类名来看就知道它是在JDK1.5 随着JUC一起添加过来的,java.util.concurrent该包名称又被称为 JUC;提供一系列线程安全处理所需要的锁,队列等等;根据以往的分析思路:先从属性分析,之后在是构造方法,最后就是里面的业务方法了。
2.1 PriorityBlockingQueue 属性
package java.util.concurrent;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.AbstractQueue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.SortedSet;
import java.util.Spliterator;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Predicate;
import jdk.internal.access.SharedSecrets;
import jdk.internal.util.ArraysSupport;
/**
* @since 1.5
* @author Doug Lea
* @param <E> the type of elements held in this queue
*/
@SuppressWarnings("unchecked")
public class PriorityBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = 5595510919245408276L;
/** 默认大小为11 */
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/** 阻塞队列 */
private transient Object[] queue;
/** 阻塞队列大小 */
private transient int size;
/** 优先级排序的比较器 */
private transient Comparator<? super E> comparator;
/***************************** 本章的主角属性 ************************************/
/**
* ReentrantLock 锁;该锁提供了lock 获取锁和unlock 释放锁两个方法
*/
private final ReentrantLock lock = new ReentrantLock();
/*
* Condition 在等待线程和唤醒线程之间提供了一种通信机制
*/
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notEmpty = lock.newCondition();
/** CAS 自旋锁所需要计算的值 */
private transient volatile int allocationSpinLock;
/** 这个属性用于在序列化和反序列化过程中,维护与此类之前版本的兼容性 */
private PriorityQueue<E> q;
// ... ...
}
PriorityBlockingQueue队列和之前PriorityQueue队列相比,属性上多了最后四个;前面三个其主要目的是用来处理阻塞;最后的 q 属性是用来进行序列化与反序列化的;
2.1.1 PriorityBlockingQueue序列化案例
package com.toast.collection.queue;
import java.io.*;
import java.util.concurrent.PriorityBlockingQueue;
/**
* @author toast
* @time 2024/5/8
* @remark
*/
public class TestPriorityBlockingQueue {
public static void main(String[] args) {
// 创建一个 PriorityBlockingQueue 对象
PriorityBlockingQueue<String> queue = new PriorityBlockingQueue<>();
// 向队列中添加一些元素
queue.offer("TOAST-A");
queue.offer("TOAST-B");
queue.offer("TOAST-C");
try {
// 将队列对象序列化到文件中
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("queue.ser"));
outputStream.writeObject(queue);
outputStream.close();
// 从文件中反序列化队列对象
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("queue.ser"));
PriorityBlockingQueue<Integer> restoredQueue = (PriorityBlockingQueue<Integer>) inputStream.readObject();
inputStream.close();
// 打印恢复的队列内容
System.out.println("恢复的队列内容:" + restoredQueue);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2.2 PriorityBlockingQueue 构造器
/** 无参构造,默认大小11 */
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
/**
* 该构造器提供指定初始容量大小
* @param initialCapacity 初始化容量
*/
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
/**
* 该构造器提供初始容量大小和比较器
* @param initialCapacity 初始化容量
* @param comparator 比较器
*/
public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator) {
if (initialCapacity < 1) throw new IllegalArgumentException();
this.comparator = comparator;
this.queue = new Object[Math.max(1, initialCapacity)];
}
/**
* 该构造器将指定元素集合添加到队列里面;
* 主要做三件事:
* 1. c集合类型如果是SortedSet/PriorityBlockingQueue 则优先级队列的比较器就直接它c集合的
* 2. c集合不是以上两种,将集合转数组,统一数据格式,因为优先级队列也是数组;
* 3. 新增数据只会需要进行堆化处理和null检验
*/
public PriorityBlockingQueue(Collection<? extends E> c) {
/*
* 由于传入的元素集合不确定是否是有序的,所以需要进行堆化处理;
* heapify = true; 确定集合是否有序,需要进行堆化处理
* heapify = false; 已确定集合是有有序的,无需要进行堆化处理
* 什么是堆化处理?在讲解 Queue 章节时已讲解过
*/
boolean heapify = true;
/*
* 由于阻塞队列不允许为空,因此也不确定集合c中的元素是否有null;所以需要进行一次筛选
* screen = true; 不确定集合是否有null,需要进行一次筛选
* screen = false; 已确定该集合没有null元素,无需进行筛选
*/
boolean screen = true;
/*
* 如果集合类型是SortedSet;
* 则进行添加元素时,优先级队列的排序使用SortedSet的比较器进行排序处理
*/
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();
heapify = false;
}
/*
* 如果集合类型是PriorityBlockingQueue;
* 则进行添加元素时,优先级队列的排序使用PriorityBlockingQueue的比较器进行排序处理
*/
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[] es = c.toArray();
int n = es.length;
/*
* 如果集合不是数组类型的集合,则将元素转换为数组;
* 因为该优先级队列就是数组,所以数据类型保持一致;方便统一处理
*/
if (c.getClass() != java.util.ArrayList.class)
es = Arrays.copyOf(es, n, Object[].class);
// 进行元素筛选;有null抛空指针异常
if (screen && (n == 1 || this.comparator != null)) {
for (Object e : es)
if (e == null)
throw new NullPointerException();
}
// 确保元素没有null元素
this.queue = ensureNonEmpty(es);
this.size = n;
// 进行堆化处理
if (heapify) heapify();
}
2.3 PriorityBlockingQueue 堆化处理
优先级队列PriorityQueue里面提供两个方向的堆化处理,一个是将元素向下比较;一个是将元素向上比较;其优先级阻塞队列PriorityBlockingQueue也是一样,代码业务基本都没有变;和优先级队列的代码基本是一致的;如下图所示:
上图紫色就是堆化处理;具体代码如下:
private void heapify() {
final Object[] es = queue; // 获取队列数据
int n = size, i = (n >>> 1) - 1;
final Comparator<? super E> cmp;
/*
* 该队列是否指定了比较器,指定了则使用比较器进行堆化处理;
* 没有则使用自然排序进行堆化处理
*/
if ((cmp = comparator) == null)
for (; i >= 0; i--)
siftDownComparable(i, (E) es[i], es, n);
else
for (; i >= 0; i--)
siftDownUsingComparator(i, (E) es[i], es, n, cmp);
}
具体可以参考 java-01-基础篇-04 Java集合-03-Queue-CSDN博客 这一章节里面;讲解到的堆化处理业务;
2.4 PriorityBlockingQueue 新增业务
2.4.1 第一种新增:操作失败抛异常
/**
* 新增元素;属于操作失败抛异常那一类的;
* 优先级阻塞队列实现并没有抛异常;原因是该队列是无界,新增大多数不会抛异常的
* @param e 元素
*/
public boolean add(E e) {
return offer(e);
}
2.4.2 第二种新增:操作失败返回特殊值
/**
* 新增元素,新增的时候加锁;完成释放锁
* @param e 新增元素
*/
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final ReentrantLock lock = this.lock; // 获取锁
lock.lock(); // 进行锁住,其他线程进入阻塞不允许进一步操作;
int n, cap;
Object[] es;
/*
* 当队列容量满了时,进行扩容;
* 不同的时进行的是尝试性的扩容;之前都是grow()直接扩容;
* 考虑到线程安全问题,这里是尝试性的进行扩容;
*/
while ((n = size) >= (cap = (es = queue).length))
tryGrow(es, cap);
try {
final Comparator<? super E> cmp;
// 进行堆化处理,该队列使用指定的比较器进行堆化处理
if ((cmp = comparator) == null)
siftUpComparable(n, e, es);
// 否则,使用自然排序进行堆化处理
else
siftUpUsingComparator(n, e, es, cmp);
size = n + 1;
// 将唤醒一条当时队列不为空的线程进行新增
notEmpty.signal();
} finally {
lock.unlock(); // 操作完之后,进行释放锁
}
return true;
}
2.4.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止
/**
* 新增元素,如果发生阻塞,必须到新增成功才释放,否则一直阻塞下去;
* @param e 新增元素
*/
public void put(E e) {
offer(e);
}
这里第三种实现方式是直接调用第二种;可能是因为优先级队列是一个无界的缘故,不需要考虑新增已满的情况,因为只有在容量已满的情况下,才会考虑进行阻塞,但是没有界限的队列是不需要考虑的
2.4.4 第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
/**
* 新增元素,接口定义是发生阻塞,如果阻塞时长超过指定时间,则进行中断处理;
* 但是该阻塞队列并没有这样实现;而是直接调offer(e);
* 原因是优先级队列是一个容量无界限的,
* 这个方法永远不会发生阻塞
* @param e 新增元素
* @param timeout 超出时间
* @param unit 时间单位
*/
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e); // never need to block
}
2.5 PriorityBlockingQueue 删除业务
2.5.1 第一种删除:操作失败抛异常
/**
* 删除元素; 删除业务也进行了加锁处理
* @param o 要删除的元素
*/
public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = indexOf(o);
if (i == -1)
return false;
removeAt(i); // 进行真正删除
return true;
} finally {
lock.unlock();
}
}
/**
* 根据索引进行删除,真正进行删除这一块业务并没有加锁,锁是加在了其他地方
* @param i 要删除元素的索引
*/
private void removeAt(int i) {
final Object[] es = queue;
final int n = size - 1;
if (n == i) es[i] = null; // 如果索引是尾部则直接删除
else {
E moved = (E) es[n];
/* 删除赋值null进行删除;
* 问题一:队列不是说不允许存储空吗?这里删除也就是null覆盖元素;null还是存在的呀
* 答案:在进行堆化处理的时候,null 视作一个普通元素;和其他元素一样,
* 也会进行排序,但不会有干扰到其他元素;
*/
es[n] = null;
final Comparator<? super E> cmp;
// 元素被删除之后,需要进行一次堆化处理;堆化处理的时候会进行一次排序
if ((cmp = comparator) == null) // 有比较器使用比较器进行堆化处理
siftDownComparable(i, moved, es, n);
else // 否则使用自然排序进行堆化处理
siftDownUsingComparator(i, moved, es, n, cmp);
if (es[i] == moved) {
if (cmp == null)
siftUpComparable(i, moved, es);
else
siftUpUsingComparator(i, moved, es, cmp);
}
}
size = n;
}
2.5.2 第二种删除:操作失败返回特殊值
/** 删除元素*/
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue(); // 删除元素
} finally { // 无论删除是否成功都将释放锁
lock.unlock();
}
}
2.5.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 获取锁与lock()方法不同的是,该过程可中断
lock.lockInterruptibly();
E result;
try {
while ( (result = dequeue()) == null) // 逐个删除头部元素
notEmpty.await(); // 如果队列为空,则线程陷入阻塞
} finally { // 无论删除是否成功都将释放锁
lock.unlock();
}
return result;
}
2.5.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
/**
* 删除元素;如果队列为空,发生阻塞,阻塞到指定时长则中断处理
* @param timeout 阻塞时间
* @param unit 时间单位
*/
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;
}
2.5.5 dequeue() 删除
private E dequeue() {
final Object[] es;
final E result;
// 删除头部元素
if ((result = (E) ((es = queue)[0])) != null) {
final int n;
final E x = (E) es[(n = --size)];
es[n] = null;
// 头部元素删除之后,进行堆化处理
if (n > 0) {
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
siftDownComparable(0, x, es, n);
else
siftDownUsingComparator(0, x, es, n, cmp);
}
}
return result;
}
2.5.6 其他Collection 旗下的删除业务
/** 删除指定元素集 */
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return bulkRemove(e -> c.contains(e));
}
private boolean bulkRemove(Predicate<? super E> filter) {
final ReentrantLock lock = this.lock;
lock.lock(); // 获取锁,进行锁住
try {
final Object[] es = queue;
final int end = size;
int i;
// 跳过队列中不包含 指定集合中的元素
/* filter.test((E) es[i]) // 返回true 或者false 具体要看,lambad表达式定义时是怎么定义的
在removeAll方法中,return bulkRemove(e -> c.contains(e)); e -> c.contains(e) 这个就是定义是否被包含指定的元素;*/
for (i = 0; i < end && !filter.test((E) es[i]); i++) ;
if (i >= end) return false; // 索引大于等于尾部值,意味着指定元素集没有一个是队列锁包含的
final int beg = i;
final long[] deathRow = nBits(end - beg); // 根据long长度64来计算数组大小
deathRow[0] = 1L; // set bit 0
for (i = beg + 1; i < end; i++)
if (filter.test((E) es[i])) setBit(deathRow, i - beg);
int w = beg;
for (i = beg; i < end; i++)
if (isClear(deathRow, i - beg)) es[w++] = es[i];
for (i = size = w; i < end; i++) es[i] = null;
// 指定元素被删除之后,需要进行堆化排序处理;
heapify();
return true;
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取锁与lock()方法不同的时,该方法获取锁等待过程中可以被中断
E result;
try {
while ( (result = dequeue()) == null) // 不断删除头部元素
notEmpty.await(); // 如果队列为空,则线程陷入等待
} finally {
lock.unlock(); // 释放锁
}
return result;
}
private E dequeue() {
final Object[] es;
final E result;
// 删除头部元素
if ((result = (E) ((es = queue)[0])) != null) {
final int n;
final E x = (E) es[(n = --size)];
es[n] = null;
// 头部元素删除之后,进行堆化处理
if (n > 0) {
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
siftDownComparable(0, x, es, n);
else
siftDownUsingComparator(0, x, es, n, cmp);
}
}
return result;
}
2.6 PriorityBlockingQueue 检查业务
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (E) queue[0];
} finally {
lock.unlock();
}
}
2.7 PriorityBlockingQueue 扩容机制
private void tryGrow(Object[] array, int oldCap) {
lock.unlock(); // 释放锁,以便其他线程可以访问队列
Object[] newArray = null;
// 通过自旋锁将allocationSpinLock值设置成1 确保只有一个线程进行
if (allocationSpinLock == 0 &&
ALLOCATIONSPINLOCK.compareAndSet(this, 0, 1)) {
try {
// 计算新容量:如果旧的容量小于64,那么新容量只会加2;否则减半
int growth = (oldCap < 64)
? (oldCap + 2)
: (oldCap >> 1);
int newCap = ArraysSupport.newLength(oldCap, 1, growth);
if (queue == array)
newArray = new Object[newCap];
} finally {
allocationSpinLock = 0; // 无论内存大小是否分配成功,最终将自旋锁计算的值还原
}
}
// newArray 不为空,意味着其他线程正在分配当中,有优雅的让出CPU时间分片;
if (newArray == null) Thread.yield();
lock.lock(); // 重新获取锁
// 如果分配成功了,将旧数据拷贝到新数组里面
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
2.8 java.lang.invoke.VarHandle
在扩容机制当中,可以看见ALLOCATIONSPINLOCK.compareAndSet(this, 0, 1) 这一行代码进行CAS 自旋锁处理; 该VarHandle类是JDK9之后提供的;这个类里面提供了原子操作支持。比如CAS(compareAndSet()), getAndAdd(), getAndSet()等等;还支持其他操作比如直接内存操作;还提供了类型安全性检查校验等等;
private static final VarHandle ALLOCATIONSPINLOCK;
/*该静态代码块的作用就是在类加载时初始化一个 VarHandle 对象 */
static {
try {
/*
获取 Lookup 查找实体类,该实体类提供创建方法来给开发者动态调用;是一个工具类;
比如:
findConstructor()、
findStatic()、
findVirtual()
这些方法来创建方法句柄
*/
MethodHandles.Lookup l = MethodHandles.lookup();
/*
* findVarHandle()通过指定class 类型,字段名称,字段类型;
* 获取对应VarHandle 变量处理类;该类可以直接操作内存中的变量,
* 也就是说可以在直接内存中进行设置allocationSpinLock字段值,避免了反射机制的一些开销。
*/
ALLOCATIONSPINLOCK = l.findVarHandle(PriorityBlockingQueue.class,
"allocationSpinLock",
int.class);
} catch (ReflectiveOperationException e) {
/*如果查找 VarHandle 的过程中出现了异常(如字段不存在或者权限不足等)抛异常*/
throw new ExceptionInInitializerError(e);
}
}
在JDK 8 里面CAS的操作处理是由sun.misc.Unsafe类来提供了(JDK9 模块化以后,该类的全限定名称为:jdk.internal.misc.Unsafe;属于JDK内部的),Unsafe部分代码如下:
像Unsafe类是Java中的一个不安全类,它提供一些底层操作,比如直接内存操作,原子性操作等等,虽然Unsafe类提供了很多强大的功能,但它也存在一些问题,导致不推荐在普通的Java代码里面使用了。
所以在JDK9 提供VarHandle,该提供一种安全的,支持类型校验,更好的一种替代方案,用于执行原子操作和内存方法。与“Unsafe”不同的是 VarHandle 提供了一种更为安全和稳定的方式来进行底层的内存操作和原子操作。
三,LinkedBlockingQueue
该队列是由链表组成的,并且是线程安全的;毕竟是属于BlockingQueue的子类;
直接源码分析
3.1 LinkedBlockingQueue 属性
package java.util.concurrent;
import java.util.AbstractQueue;
/**
* @since 1.5
* @author Doug Lea
* @param <E> the type of elements held in this queue
*/
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -6903933977591709194L;
/** 表链节点类. */
static class Node<E> {
E item; /*元素内容*/
Node<E> next; /* 下一个节点,由于是单向队列,所以只有一个方向 */
Node(E x) { item = x; }
}
/** 链表容量,默认是Integer.MAX_VALUE; 该队列容量是有边界的,不是无限增长的 */
private final int capacity;
/** 统计容量 */
private final AtomicInteger count = new AtomicInteger();
/** 头节点 */
transient Node<E> head;
/** 尾部点 */
private transient Node<E> last;
/** 由take、poll操作持有的锁 用于控制消费数据的锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/*
* Condition 在等待线程和唤醒线程之间提供了一种通信机制
*/
/** 等待take的等待队列 所有用于消费数据的删除操作线程都在notEmpty等待中 */
@SuppressWarnings("serial")
private final Condition notEmpty = takeLock.newCondition();
/** 由put, offer 操作持有的锁 用于控制生产数据的锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** 等待puts操作的等待队列 所有用于生产数据的新增操作线程都在notFull等待中*/
@SuppressWarnings("serial")
private final Condition notFull = putLock.newCondition();
// ......
}
3.2 LinkedBlockingQueue 构造器
/* 无参构造,创建一个默认大小为Integer.MAX_VALUE 最大值 */
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* 创建一个指定容量大小阻塞队列
* @param capacity 容量大小
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null); // 初始化链表头和链表尾都是一个null节点
}
/**
* 创建一个阻塞队列,并将指定的元素集合传入到阻塞队列里面
* @param c 集合元素
*/
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock; // 获取 put 操作的锁
putLock.lock();
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;
}
// 同步计算追加的个数,这个set方法是AtomicInteger内置的,专门为线程的原子操作提供的
count.set(n);
} finally { // 最后进行锁释放
putLock.unlock();
}
}
3.3 LinkedBlockingQueue 新增业务
3.3.1 第一种新增:操作失败抛异常
LinkedBlockingQueue 直接调用AbstractQueue 的add();
/**
* 新增元素
* @param e 要新增的元素
*/
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
3.3.2 第二种新增:操作失败返回特殊值
/**
* 新增元素,在新增元素时,获取putLock 将其锁住使其他offer,put等操作新增,进入等待队列;
*/
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
final int c;
final Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock; // 获取锁
putLock.lock(); // 进行锁住,其他的线程进入到这里,进入等待,或者其他线程调用put时,获取lock也会进入等待;
try {
if (count.get() == capacity) return false;
enqueue(node);
c = count.getAndIncrement();
// 如果新增完容量还有剩余,将唤醒另外一条线程执行新增操作(生产数据)
if (c + 1 < capacity)
notFull.signal();
} finally { // 无论本次新增是否成功都将释放锁
putLock.unlock();
}
// 新增元素操作已完成,但立刻又被消费者全部消费完(所以就是 c == 0 表示队列为空); 所以就唤醒一条正在等待生产者线程,并且生产者能力以此也会接近消费者的能力
if (c == 0)
signalNotEmpty();
return true;
}
/** 链表尾部追加节点元素 */
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
新增业务,在多线程并发的时候,会先进行锁住,让其他的offer(),put()等新增元素操作进入等待中。然后再从尾部追加数据;追加完成之后,如果容量还有剩余的;那么将会继续唤醒一条正在等待offer或put线程再一次新增;依次循环反复;直到没有新增线程等待或者是容量已满;
不管新增元素是否成功,都会释放锁,因为该阻塞链表是有界的;当容量满了,但是还是十条线程需要添加元素;都会添加失败;所以不管新增元素是否成功都会释放锁!
如果新增元素的时候,队列为空,那么本次新增的元素为第一个,则会唤醒一条消费线程进行消费;因为新增元素的时候,还有正在等待消费的。
3.3.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止
/**
* 新增元素
* @param e 要新增的元素
*/
public void put(E e) throws InterruptedException {
// 新增元素为null,抛空指针
if (e == null) throw new NullPointerException();
final int c;
final Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* while 循环判断队列是否已满,已满则进入阻塞
* 等队列不满的时候,唤醒线程并完成后续代码
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node); // 追加元素
c = count.getAndIncrement(); // 计数+1
if (c + 1 < capacity) // 如果容量还有剩余,则唤醒另一条添加元素线程
notFull.signal();
} finally { // 无论新增释放成功都将释放锁
putLock.unlock();
}
// 新增元素操作已完成,但立刻又被消费者全部消费完(所以就是 c == 0 表示队列为空); 所以就唤醒一条正在等待生产者线程,并且生产者能力以此也会接近消费者的能力
if (c == 0)
signalNotEmpty();
}
从代码中发现,阻塞情况时在队列容量已经满了,还有其他线程往里面追加元素,此时后面的线程则进入while 循环调用 notFull.await(); 方法进入阻塞状态!而且是一直阻塞!
3.3.4 第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
/** 新增元素,在指定的时间内尝试向队列中添加元素,如果超过了指定的时间还未成功添加,则返回 false*/
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
// 将超时时间转换为纳秒,以便与 awaitNanos 方法一起使用。
long nanos = unit.toNanos(timeout);
final int c;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); // 进行锁住,该锁可中断
try {
// 使用 while 循环来检查队列是否已满,如果队列已满则进入等待状态,直到超时或者队列有空间可用为止。
while (count.get() == capacity) {
if (nanos <= 0L)
return false;
nanos = notFull.awaitNanos(nanos); // 使线程进入等待
}
enqueue(new Node<E>(e)); // 追加元素
c = count.getAndIncrement();
if (c + 1 < capacity) // 如果容量还有剩余,则唤醒另一条添加元素线程
notFull.signal();
} finally { // 无论新增释放成功,都将释放锁
putLock.unlock();
}
// 新增元素操作已完成,但立刻又被消费者全部消费完(所以就是 c == 0 表示队列为空); 所以就唤醒一条正在等待生产者线程,并且生产者能力以此也会接近消费者的能力
if (c == 0)
signalNotEmpty();
return true;
}
put方法,也是在容量满了,后面新增的线程将会进入while循环调用awaitNanos(nanos)该方法使线程进入阻塞;但是有时间限制;
所以,对于阻塞和有时限的阻塞是通过Condition的await()和awaitnanos()来实现的!
3.3.5 enqueue
/** 链表尾部追加节点元素 */
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
3.4 LinkedBlockingQueue 删除业务
3.4.1 第一种删除:操作失败抛异常
也是直接调用父类AbstractQueue的模板方法
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
3.4.2 第二种删除:操作失败返回特殊值
/** 删除元素 */
public E poll() {
final AtomicInteger count = this.count; // 获取当前数量
if (count.get() == 0) // 队列内容为空,直接返回不处理
return null;
final E x;
final int c;
final ReentrantLock takeLock = this.takeLock; // 获取takeLock锁
takeLock.lock(); // 进行锁住,后面其他remove,take等操作都将进入等待
try {
if (count.get() == 0) // 再一次进行空判断;
return null;
x = dequeue(); // 进行元素删除(消费数据)
c = count.getAndDecrement(); // 计数-1
if (c > 1) // 如果容量大于1,则在唤醒一条删除线程进行删除(消费数据)
notEmpty.signal();
} finally { // 最后不管是否删除成功,都将释放锁
takeLock.unlock();
}
// 当前删除操作已完成,但立刻又被生产者线程将队列填满(所以就 c == capacity), 就唤醒一条正在等待的消费线程,以此提高消费者能力和生产者能力接近
if (c == capacity) signalNotFull();
return x;
}
3.4.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止
/* 删除元素,发生阻塞则一直阻塞下去一直到成功为止 */
public E take() throws InterruptedException {
final E x;
final int c;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) { // 没有元素可删除时
notEmpty.await(); // 当前线程进入阻塞
}
x = dequeue(); // 进行删除
c = count.getAndDecrement(); // 计数-1
if (c > 1) // 如果队列还有元素,则唤醒一条删除线程进行删除(进行消费数据)
notEmpty.signal();
} finally { // 最后不管是否删除成功,都将释放锁
takeLock.unlock();
}
// 当前删除操作已完成,但立刻又被生产者线程将队列填满(所以就 c == capacity), 就唤醒一条正在等待的消费线程,以此提高消费者能力和生产者能力接近
if (c == capacity)
signalNotFull();
return x;
}
3.4.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
final E x;
final int c;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly(); // 获取锁,该锁可中断处理
try {
while (count.get() == 0) { // 没有元素可删除时,线程进入阻塞
if (nanos <= 0L)
return null;
nanos = notEmpty.awaitNanos(nanos); // 设置阻塞时间,线程进入阻塞
}
x = dequeue(); // 删除元素
c = count.getAndDecrement(); // 计数-1
if (c > 1) // 如果队列还有元素,则唤醒一条删除线程进行删除(进行消费数据)
notEmpty.signal();
} finally { // 最后不管是否删除成功,都将释放锁
takeLock.unlock();
}
// 当前删除操作已完成,但立刻又被生产者线程将队列填满(所以就 c == capacity), 就唤醒一条正在等待的消费线程,以此提高消费者能力和生产者能力接近
if (c == capacity)
signalNotFull();
return x;
}
3.4.5 dequeue
/*
* 删除元素的真正业务:该队列是FIFO 先进先出;所以删除从头节点入手
* 链表结构:head --> first --> second --> three --> ... --> tail
*/
private E dequeue() {
Node<E> h = head; // 获取当前头部节点
Node<E> first = h.next; // 当前头部节点的后置节点为新的头部节点
h.next = h; // help GC 帮助GC垃圾回收器识别不可达对象
head = first; // 新头部节点诞生
E x = first.item; // 新头部节点元素
first.item = null;
return x;
}
3.5 LinkedBlockingQueue 检查业务
public E peek() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null;
/*
* 获取tackLock,这里也明明是查询也会获取消费的业务锁,
* 这样做是为了在获取时保持数据的一致性;
* 因为在多线程情况下,获取元素的时候,说不定正在执行删除操作;删除操作时,说不定也正在执行检索操作
* 所以加锁处理,检索时,删除的业务阻塞,删除时,检索的业务也会阻塞
*/
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
return (count.get() > 0) ? head.next.item : null;
} finally {
takeLock.unlock();
}
}
3.6 LinkedBlockingQueue 扩容机制
该LinkedBlockingQueue 没有扩容机制。因为它是一个容量有限制的集合,所以没有扩容处理;当元素添加满了之后,接下来的添加元素业务将会陷入阻塞状态。并且会唤醒一条正在等待队列消费线程进行数据的消费;毕竟队列里面有数据可以消费;
3.7 LinkedBlockingQueue 相关阻塞业务
LinkedBlockingQueue阻塞队列里面的阻塞业务主要分为两个,一个生产业务的阻塞,一个是消费业务的阻塞;生产者就是新增操作;消费就是删除操作;简单点就是生产者与消费者;生产的数据往队列里面添加;消费者消费队列的数据;
生产者一直生产队列满的时候,将停止生产进入阻塞,并且将唤醒一条正在等待消费线程进行消费;消费业务也是一样的,消费者进行消费,直到队列没有数据将停止消费并进入阻塞,同时也会唤醒一条正在等待生产的线程进行数据的生产;这样就即满足了生产者与消费者之间的基本业务;
在分析属性的时候,提到如下几个属性,提供一个用于阻塞消费锁的属性takeLock; 同时也提供一个用于阻塞生产锁的属性 putLock;这两个属性的作用起到屏障的作用,同一时刻只能有一个业务处理;如果是takeLock.lock();进行锁住就是同一时刻只能有一个消费线程处理;生产者同理;putLock.lock();进行锁住就是同一时刻只能有一个生产线程处理;只有释放锁才能有下一个进入;
/** 由take、poll操作持有的锁 用于控制消费数据的锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 等待take的等待队列 所有用于消费数据的删除操作线程都在notEmpty等待中 */
@SuppressWarnings("serial")
private final Condition notEmpty = takeLock.newCondition();
/** 由put, offer 操作持有的锁 用于控制生产数据的锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** 等待puts操作的等待队列 所有用于生产数据的新增操作线程都在notFull等待中*/
@SuppressWarnings("serial")
private final Condition notFull = putLock.newCondition();
而对应的提供对应Condition ; 什么是Condition? Condition 在等待线程和唤醒线程之间提供了一种通信机制;生产者的condition 的属性是notFull; 消费者的属性condition是 notEmpty;
condition 可以让线程从阻塞状态进行唤醒;也可以让正在处理的线程进入阻塞状态;比如在新增业务当中举例子:
获取putlock 锁,并调用putLock.lock();进行锁住;同一时刻只能有一个生产者进入;这里一锁住,其他地方调用put/offer(e, time) 等新增元素也会被阻塞,因为put/offer(e, time)这些接口也需要获取到putLock锁能才进行下去;所以这里putLock.lock();就保证同一时刻只有一个新增元素业务;
并且在 容量还有剩余的话,就会唤醒另一条线程进行生产;并且如果队列已满则进行阻塞;notFull 这个Condition进行阻塞与唤醒;所以condition是在等待线程和唤醒线程之间提供了一种通信机制
/** 新增元素,在指定的时间内尝试向队列中添加元素,如果超过了指定的时间还未成功添加,则返回 false*/
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
// 将超时时间转换为纳秒,以便与 awaitNanos 方法一起使用。
long nanos = unit.toNanos(timeout);
final int c;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); // 进行锁住,该锁可中断
try {
// 使用 while 循环来检查队列是否已满,如果队列已满则进入等待状态,直到超时或者队列有空间可用为止。
while (count.get() == capacity) {
if (nanos <= 0L)
return false;
nanos = notFull.awaitNanos(nanos); // 使线程进入等待
}
enqueue(new Node<E>(e)); // 追加元素
c = count.getAndIncrement();
if (c + 1 < capacity) // 如果容量还有剩余,则唤醒另一条添加元素线程
notFull.signal();
} finally { // 无论新增释放成功,都将释放锁
putLock.unlock();
}
// 新增元素操作已完成,但立刻又被消费者全部消费完(所以就是 c == 0 表示队列为空); 所以就唤醒一条正在等待生产者线程,进行生产依此来接近消费者的能力
if (c == 0)
signalNotEmpty();
return true;
}
/** 由take、poll操作持有的锁 用于控制消费数据的锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 等待take的等待队列 所有用于消费数据的删除操作线程都在notEmpty等待中 */
@SuppressWarnings("serial")
private final Condition notEmpty = takeLock.newCondition();
/** 由put, offer 操作持有的锁 用于控制生产数据的锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** 等待puts操作的等待队列 所有用于生产数据的新增操作线程都在notFull等待中*/
@SuppressWarnings("serial")
private final Condition notFull = putLock.newCondition();
3.7.1 signalNotEmpty
/** 唤醒一条正在等待消费线程进行消费*/
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
3.7.2 signalNotFull
/** 唤醒一条正在等待生产线程进行生产 */
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
四,ArrayBlockingQueue
上一个阻塞队列 LinkedBlockingQueue 是用链表实现,这个是用数组实现的阻塞队列;也是一个先进先出的队列
4.1 ArrayBlockingQueue 继承结构
package java.util.concurrent;
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {}
从继承结构上分析得知,该阻塞队列是BlockingQueue的子类,并且继承了AbstractQueue抽象类。
4.2 ArrayBlockingQueue 属性
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -817911632652898426L;
/** 阻塞队列 */
@SuppressWarnings("serial")
final Object[] items;
/** 删除,检索的索引 */
int takeIndex;
/** 新增的索引 */
int putIndex;
/** 队列容量 */
int count;
final ReentrantLock lock;
/** tacks等待对象 */
@SuppressWarnings("serial")
private final Condition notEmpty;
/** put等待对象 */
@SuppressWarnings("serial")
private final Condition notFull;
/**
* 被transient 修饰,说明不会被序列化,意味着在序列化过程中会被忽略
* 它可能是用来存储当前活动迭代器的共享状态,或者如果当前没有活动迭代器,则为 null。这允许队列操作来更新迭代器的状态
*/
transient Itrs itrs;
// ......
}
4.3 ArrayBlockingQueue 构造器
/** 指定容量大小 */
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
/**
* 通过指定容量创建一个阻塞队列
*/
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();
}
/**
* 创建指定容量大小,并且将指定元素集添加到队列
* @param capacity 容量大小
* @param fair 是否开启公平锁
* @param c 元素集
*/
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Object[] items = this.items;
int i = 0;
try {
// for 逐个追加到队列里面,并且校验每个元素不能为null, 有null元素的话抛异常
for (E e : c)
items[i++] = Objects.requireNonNull(e);
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
/* putIndex = 0 意味着队列容量已满,下一位新增的元素的从头开始
* putIndex = i 意味着队列容量还有剩余位置,记录下一位新增元素的索引 i */
putIndex = (i == capacity) ? 0 : i;
} finally { // 无论是否添加成功都将释放锁
lock.unlock();
}
}
4.4 ArrayBlockingQueue 新增业务
4.4.1 第一种新增:操作失败抛异常
/** 新增方法直接调用 AbstractQueue */
public boolean add(E e) {
return super.add(e);
}
/** AbstractQueue 的新增模板方法 */
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
4.4.2 第二种新增:操作失败返回特殊值
/** 新增元素,有加锁进行阻塞 */
public boolean offer(E e) {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock; // 获取锁
lock.lock(); // 进行锁住,其他线程需要获取 this.lock锁时陷入阻塞状态
try {
if (count == items.length) // 容量已满,添加失败
return false;
else {
enqueue(e); // 添加元素
return true;
}
} finally { // 无论是否添加成功,都将释放锁
lock.unlock();
}
}
4.4.3 第三种新增:操作发生阻塞,一直阻塞下去,不会中断,直到新增成功为止
/** 新增元素 */
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
/*
* 如果容量已满,则线程一直陷入阻塞状态
* 并且调用await()方法会暂时释放锁,给其他线程使用
* 等到count < items.length; 有容量的时候,线程会被唤醒,并获取当前锁,
* 继续执行后续的代码
*/
while (count == items.length)
notFull.await();
enqueue(e); // 队列容量没有满,则添加元素
} finally {// 无论是否新增元素都会释放锁
lock.unlock();
}
}
4.4.4 第四种新增:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
/** 新增元素-阻塞指定时间 */
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
Objects.requireNonNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取锁,中途发生中断,则抛中断异常
try {
while (count == items.length) { // 容量已满,则陷入阻塞状态
if (nanos <= 0L) // 阻塞时长小于等于0,则直接返回
return false;
// 进行阻塞,并暂时性释放锁,给其他线程使用
nanos = notFull.awaitNanos(nanos);
}
enqueue(e); // 新增元素
return true;
} finally { // 无论是否新增成功,都将释放锁
lock.unlock();
}
}
4.4.5 enqueue
private void enqueue(E e) {
final Object[] items = this.items;
items[putIndex] = e; // 直接往最后一个索引位置追加
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal();
}
4.5 ArrayBlockingQueue 删除业务
4.5.1 第一种删除:操作失败抛异常
/** 删除元素 - 属于操作失败抛异常 */
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
4.5.2 第二种删除:操作失败返回特殊值
/** 删除元素 */
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 队列没有元素则返回null,否则执行删除业务
return (count == 0) ? null : dequeue();
} finally {// 无论删除是否成功都将释放锁
lock.unlock();
}
}
4.5.3 第三种删除:操作发生阻塞,一直阻塞下去,不会中断,直到删除成功为止
/**删除元素-如果队列为空,进入阻塞,直到删除成功才结束*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取锁
try {
while (count == 0) // 队列为空,则进入阻塞当中,并暂时释放当前锁,给其他线程使用
notEmpty.await();
return dequeue(); // 直接删除元素
} finally {// 无论元素是否删除成功都将释放锁
lock.unlock();
}
}
4.5.4 第四种删除:操作发生阻塞,阻塞时长超出指定时长,则中断处理!或者操作成功也会释放锁
/** 删除元素-如果发生阻塞则只会阻塞到指定时长 */
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取锁,中途可进行中断
try {
while (count == 0) {
if (nanos <= 0L) // 队列为空则进行删除
return null;
nanos = notEmpty.awaitNanos(nanos); // 阻塞指定时间
}
return dequeue(); // 删除元素
} finally { // 无论是否删除成功,都将释放锁
lock.unlock();
}
}
4.6 ArrayBlockingQueue 检验业务
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return itemAt(takeIndex);
} finally { // 无论是否拿到数据都将释放锁
lock.unlock();
}
}
4.7 ArrayBlockingQueue 获取剩余数量
public int remainingCapacity() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return items.length - count;
} finally {
lock.unlock();
}
}