什么是队列
队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。
Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Deque接 口。
Queue-队列数据实现。分为阻塞队列和非阻塞队列,下列蓝色区快,为阻塞队列持有方法
灰色部分的持有方法如果失败会直接抛出异常
非阻塞队列
offer: (非阻塞)将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false,不会抛异常,add()方法队列已满,则抛出一个IIIegaISlabEepeplian异常:
poll:是从队列中删除第一个元素,poll() 方法在用空集合调用时只是返回 null;remove()方法功能类似,集合为空,则抛出一个NoSuchElementException异常
peek:用于在队列的头部查询元素,元素不会出队列,在队列为空时peek() 返回 null。element()方法功能类似,队列为空,则抛出一个NoSuchElementException异常
阻塞队列
put:将指定的元素插如队列中,等待可用空间,阻塞队列,直到能够有空间插入元素,就是有其他线程调用poll()、take()、remove()方法元素出队列空间释放插入成功,不成功则一直等待,底层实现如下代码
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();
}
}
take:获取并移除此队列的头部,在元素变得可用之前一直等待 。queue的长度 == 0 的时候,一直阻塞,底层代码如下
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果队列数量为0,线程一直挂起等待,直到数量大于0,返回队列元素锁释放
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
并发Queue
在并发的队列上jdk提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论在那种都继承自Queue。
如图下,继承Queue的主要方法,
ConcurrentLinkedQueue
概念理解
ConcurrentLinkedQueue:是一个适用于高并发场景下的队列,通过无锁的方式,是一个非阻塞队列,不提供阻塞方法,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueueo它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。
poll() offer()两个方法都是用CAS实现,offer()方法使用CAS操作替换链表的尾部入队列的操作,poll()方法,将链表的头部使用CAS操作替换成null出队列;
/**
*在此队列的尾部插入指定的元素。
*由于队列是无界的,此方法永远不会返回{@code false}。
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, newNode)) {
// CAC操作成功修改
// 为了使e成为这个队列的一个元素,
// 让newNode 编程当前 队列元素
if (p != t) // 一次跳两个节点
casTail(t, newNode); // 这里失败是可以的
return true;
}
// 丢失的CAS争用另一个线程;下一步重新读取
}
else if (p == q)
p = (t != (t = tail)) ? t : head;
else
// 两次跳跃后检查尾部更新
p = (p != t && t != (t = tail)) ? t : q;
}
}
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
// Successful CAS is the linearization point
// 从队里中删除元素
if (p != h) // 一次跳两个节点
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
实例代码
public void test03_ConcurrentLinkedQueue(){
//非阻塞的度列
ConcurrentLinkedQueue<String> conQueue = new ConcurrentLinkedQueue<>();
conQueue.offer("a");
conQueue.offer("b");
conQueue.offer("c");
conQueue.offer("d");
conQueue.offer("e");
//从头部取出元素,并从队列里删除
System.out.println("从头部取出元素,并从队列里删除 >> "+conQueue.poll());
System.out.println("删除后的长度 >> "+conQueue.size()); //4
System.out.println("取出头部元素 >> "+conQueue.peek()); //b
System.out.println("长度 >> "+conQueue.size()); //4
//不会提供阻塞方法
//conQueue.put("");
//conQueue.take();
}
BlockingQueue接口
ArrayBlockingQueue
基于数组的阻塞队列实现的一个阻塞式队列,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费不能完全并行,长度是需要定义的,有界也就意味着,它不能够存储无限多数量的对象。所以在创建 ArrayBlockingQueue 时,必须要给它指定一个队列的大小可以指定先讲先出或者先讲后出,也叫有界队列,在很多场合非常适合使用。
- 先进先出队列(队列头的是最先进队的元素;队列尾的是最后进队的元素)
- 有界队列(即初始化时指定的容量,就是队列最大的容量,不会出现扩容,容量满,则阻塞进队操作;容量空,则阻塞出队操作)
- 队列不支持空元素
/** 队列是Object数组 */
final Object[] items;
/** 记录出队列下标 执行 take, poll, peek or remove */
int takeIndex;
/** 存入队列下标 执行 put, offer, or add */
int putIndex;
/** 队列中的元素数量 */
int count;
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** 全局锁 全局共享。都会使用这个锁*/
final ReentrantLock lock;
/** 读取队列等待池 */
private final Condition notEmpty;
/** 新增队列等待池 */
private final Condition notFull;
从主要的成员变量可以看出 ArrayBlockingQueue 进队操作采用了加锁的方式保证并发安全。并发执行是同步的,也就是在高并发的场景属于是单线程执行
LinkedBlockingQueue
基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也维持着一个数据缓冲队列〈该队列由一个链表构成),LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作的完全并行运行,他是一个无界队列。不存在线程安全问题,并发高于ArrayBlockingQueue,
// 创建一个 核心线程数量为5,最大数量为10,等待队列最大是3 的线程池,也就是最大容纳13个任务。
// 默认的策略是抛出RejectedExecutionException异常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("有任务被拒绝执行了");
}
});
常用做线程池实现
/** 队列最大值,如果没有定义就是int最大值 */
private final int capacity;
/** 当前元素数 数量,原子操作线程安全 */
private final AtomicInteger count = new AtomicInteger();
/**
* 定义链表头部
* Invariant: head.item == null
*/
transient Node<E> head;
/**
* 链表的尾部
* Invariant: last.next == null
*/
private transient Node<E> last;
/** 读锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/** 读取元素等待队列*/
private final Condition notEmpty = takeLock.newCondition();
/** 写锁 */
private final ReentrantLock putLock = new ReentrantLock();
/** 新增元素等待队列*/
private final Condition notFull = putLock.newCondition();
从类方法成员变量可以看出, LinkedBlockingQueue使用了读写锁互斥锁,读取数据性能更高,链表结构数组
SynchronousQueue同步队列,没有容量的队列
一种没有缓冲的队列,生产者产生的数据直接会被消费者获取并消费。
- take会阻塞,直到取到元素
- put时会阻塞,直到被get
- 若没有take方法阻塞等待,offer的元素可能会丢失
- poll取不到元素,就返回null,如果正好有put被阻塞,可以取到
- peek 永远只能取到null,不能让take结束阻塞
- 对于一般的队列来说实例化队列后就可以存入,同步队列初始化后容量为零,进入阻塞,当我们去take获取队列才能put进去,可以说入队列和出队列同步执行;
- 执行offer(非阻塞)数据可能会丢失,执行获取队列为null;
- 使用场景,有一个缓存线程池,用的就是同步队列实现
/*
1、take会阻塞,直到取到元素
2、put时会阻塞,直到被get
3、若没有take方法阻塞等待,offer的元素可能会丢失
4、poll取不到元素,就返回null,如果正好有put被阻塞,可以取到
5、peek 永远只能取到null,不能让take结束阻塞
*/
public class Demo2_SyncQueueTest {
static SynchronousQueue<String> syncQueue = new SynchronousQueue<>();
//put时会阻塞,直到被get
public static void test01() throws InterruptedException {
new Thread(){
@Override
public void run() {
try {
Thread.sleep(3000L);
System.out.println(syncQueue.poll());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
System.out.println("begain to put...");
syncQueue.put("put_element");
System.out.println("put done...");
}
//3、若没有take方法阻塞等待,offer的元素可能会丢失
public static void test02() throws InterruptedException {
syncQueue.offer("offered_element");
System.out.println(syncQueue.poll());
}
//4、poll取不到元素,就返回null,如果正好有put被阻塞,可以取到
public static void test03() throws InterruptedException {
/* new Thread(){
@Override
public void run() {
try {
syncQueue.put("put_element");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();*/
Thread.sleep(200L);
Object obj = syncQueue.poll();
System.out.println(obj);
}
//peek 永远只能取到null,不能让take结束阻塞
public static void test04() throws InterruptedException {
new Thread(){
@Override
public void run() {
try {
syncQueue.put("put_element");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
Thread.sleep(200L);
Object obj = syncQueue.peek();
System.out.println(obj);
}
public static void main(String args[]) throws InterruptedException {
test02();
}
}
PriorityBlockingQueue优先级队列,非先进先出 可以指定优先级
public class Demo4_PriorityBlockingQueue3 {
public static void main(String args[]){
PriorityBlockingQueue<Student> queue = new PriorityBlockingQueue<>(5, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int num1 = o1.age;
int num2 = o2.age;
if (num1 > num2)
return 1;
else if (num1 == num2)
return 0;
else
return -1;
}
});
queue.put(new Student(10, "enmily"));
queue.put(new Student(20, "Tony"));
queue.put(new Student(5, "baby"));
for (;queue.size() >0;){
try {
System.out.println(queue.take().name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Student{
public int age;
public String name;
public Student(int age, String name){
this.age = age;
this.name = name;
}
}
优先级可以指定,实列代码自定义按照学生的年龄进行出队列