1、定义


队列是一种特殊的线性表,遵循的原则就是 “ 先入先出 ” 。在我们日常使用中,经常会用来并发操作数


据。在并发编程中,有时候需要使用线程安全的队列。如果要实现一个线程安全的队列通常有两种方


式:一种是使用阻塞队列,另一种是使用线程同步锁


存储结构


类似栈有顺序队和链式队两种。


我们可以围绕栈的 4 个元素来实现队列:


2 状态:是否队空;是否队满。


2 操作:进队 push; 出队 pop 。


顺序队的实现


顺序队列的实现也可以使用数组来完成,同栈的实现一样,只是栈是在同一端进行压栈和进栈操作,



而队列是在一端做 push ,另一端做 pop 操作。



可以看一下下面的队列操作示意图:



java什么时候使用violate java什么时候使用队列_java什么时候使用violate



我们在实现顺序栈时使用头指针 “front” 和尾指针 “rear” 分别进行出队和入队操作,但普通的队列如上图



所示,会发生 “ 假溢出 ” 现象,所以我们通常将数组弄成一个环状,即队头和队尾相连,这样就形成了 “ 循



环队列 ” ,同时也解决了 “ 假溢出 ” 现象。循环队列是改进版的顺序队列。



如图:



java什么时候使用violate java什么时候使用队列_java什么时候使用violate_02



对于普通队列的 push 或 pop 我们只需要对尾指针或头指针进行自增操作即可,但是循环队列我们就



不能单纯的进行自增,当 front 或 rear=maxSize-1 时我们就不能进行自增操作了,比如一个队列尾长度



为 4 的数组 datas[4] ,那么当 front 或 rear 需要在 0,1,2,3 之间进行循环 “ 推进 ” ,以此达到循环队列的效果。



所以我们可以使用 rear = ( rear+1 ) %maxSize ; front = ( front+1 ) %maxSize ;公式进行指针计



算。



需要注意的是:队空状态的条件为: front = rear 。而如果整个队列全部存满数据那么,队满的条件



也是 front = rear ;所以循环队列需要损失一个存储空间,如下图:



java什么时候使用violate java什么时候使用队列_java_03

什么是阻塞队列



假设有一个面包房,里面有一个客人吃面包,一个师傅烤面包。篮子里面最多放 2 个面包,师傅考



完了面包放到篮子里,而客人吃面包则从篮子里面往外拿,为了保证客人吃面包的时候篮子里有面



包或者师傅烤面包的时候篮子不会溢出,这时候就需要引用出来阻塞队列的概念,就是我们常说的



生产者消费者的模式。 阻塞队列是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。



1 )支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。



2 )支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。阻塞队列



常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线



程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。



系统内不阻塞队列: **PriorityQueue** ConcurrentLinkedQueue



我们来看一下不阻塞队列的关系(以 PriorityQueue 为例):



java什么时候使用violate java什么时候使用队列_java_04



PriorityQueue 类继承自 AbstractQueue ,实现了 Serializable 接口。实质上维护了一个有序列表,



PriorityQueue 位于 Java util 包中,观其名字前半部分的单词 Priority 是优先的意思,实际上这个队列就



是具有 “ 优先级 ” 。加入到 Queue 中的元素根据它们的天然排序(通过其 java.util.Comparable 实现)



或者根据传递给构造函数的 java.util.Comparator 实现来定位。



ConcurrentLinkedQueue 是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在



队列的尾部添加元素并从头部删除它们,所以不需要知道队列的大小, ConcurrentLinkedQueue 对公



共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列;



ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进



行排序,当我们添加一个元素的时候,它会添加到队列的尾部;当我们获取一个元素时,它会返回队列



头部的元素。



实现阻塞接口的队列:



java.util.concurrent 中加入了 BlockingQueue 接口和五个阻塞队列类。它实质上就是一种带有一点扭



曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素



可用。



五个队列所提供的各有不同:



ArrayBlockingQueue :一个由数组支持的有界队列。



LinkedBlockingQueue :一个由链接节点支持的可选有界队列。



PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。



DelayQueue :一个由优先级堆支持的、基于时间的调度队列。



SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集( rendezvous )机制。



我们看一下 ArrayBlockingQueue 和 LinkedBlockingQueue 的继承关系:



java什么时候使用violate java什么时候使用队列_java_05

java什么时候使用violate java什么时候使用队列_阻塞队列_06



通过查看两个类的继承关系,我们可以知道,他们也是继承自 AbstractQueue ,实现了 Serializable 接



口;不同的是他们同时实现了 BlockingQueue 接口。



简单介绍下其中的几个:



LinkedBlockingQueue LinkedBlockingQueue 默认大小是 Integer.MAX_VALUE ,可以理解为一个缓存



的有界等待队列,可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO (先进先出)排序



元素。当生产者往队列中放入一个数据时,缓存在队列内部,当队列缓冲区达到最大值缓存容量时



( LinkedBlockingQueue 可以通过构造函数指定该值),阻塞生产者队列,直到消费者从队列中消费掉



一份数据,生产者线程会被唤醒,反之对于消费者同理。



ArrayBlockingQueue 在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置



true ,等待时间最长的线程会优先得到处理(其实就是通过将 ReentrantLock 设置为 true 来 达到这种公



平性的:即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常



需要的时候再使用它。它是基于数组的阻塞循环队列,此队列按 FIFO (先进先出)原则对元素进行排



序。



PriorityBlockingQueue 是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,



该队列也没有上限(看了一下源码, PriorityBlockingQueue 是对 PriorityQueue 的再次包装,是基于堆



数据结构的,而 PriorityQueue 是没有容量限制的,与 ArrayList 一样,所以在优先阻塞 队列上 put 时是



不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致



OutOfMemoryError ),但是如果队列为空,那么取元素的操作 take 就会阻塞,所以它的检索操作 take



是受阻的。另外,往入该队列中的元 素要具有比较能力。



关于 ConcurrentLinkedQueueLinkedBlockingQueue:



也可以理解为阻塞队列和非阻塞队列的区别: 1.LinkedBlockingQueue 是使用锁机制, ConcurrentLinkedQueue 是使用 CAS 算法,虽然



LinkedBlockingQueue 的底层获取锁也是使用的 CAS 算法



2. 关于取元素, ConcurrentLinkedQueue 不支持阻塞去取元素, LinkedBlockingQueue 支持阻塞的



take() 方法。



3. 关于插入元素的性能,但在实际的使用过程中,尤其在多 cpu 的服务器上,有锁和无锁的差距便体现



出来了, ConcurrentLinkedQueue 会比 LinkedBlockingQueue 快很多。



生产者消费者代码:



import java.util.concurrent.ArrayBlockingQueue; 
import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
public class BlockingQueueTest { 
    public static class Basket { 
        BlockingQueue<String> basket = new ArrayBlockingQueue<>(3); 
        private void produce() throws InterruptedException { 
            basket.put("苹果"); 
        }
        private void consume() throws InterruptedException { 
            basket.take(); 
        }
        private int getAppleNumber() { 
            return basket.size(); 
        } 
    }
    private static void testBasket() { 
        final Basket basket = new Basket(); 
        class Producer implements Runnable { 
            public void run() { 
                try {
                    while (true) { 
                        System.out.println("生产者开始生产苹果###"); 
                        basket.produce(); 
                        System.out.println("生产者生产苹果完毕###"); 
                        System.out.println("篮子中的苹果数量:" + basket.getAppleNumber() + "个"); 
                        Thread.sleep(300);
                     } 
                } catch (InterruptedException e) {}
            } 
       }
        class Consumer implements Runnable { 
            public void run() { 
                try {
                    while (true) { 
                        System.out.println("消费者开始消费苹果***"); 
                        basket.consume(); 
                        System.out.println("消费者消费苹果完毕***"); 
                        System.out.println("篮子中的苹果数量:" + basket.getAppleNumber() + "个"); 
                        Thread.sleep(1000); 
                    } 
                } catch (InterruptedException e) {} 
            } 
        }
        ExecutorService service = Executors.newCachedThreadPool(); 
        Producer producer = new Producer(); 
        Consumer consumer = new Consumer(); 
        service.submit(producer); 
        service.submit(consumer); 
        try {Thread.sleep(10000); } catch (InterruptedException e) {} 
        service.shutdownNow(); 
}
public static void main(String[] args) { BlockingQueueTest.testBasket(); } }