队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
在队列这种数据结构中,最先插入的元素将是最先被删除的元素;反之最后插入的元素将是最后被删除的元素,因此队列又称为“先进先出”(FIFO—first in first out)的线性表。
在并发队列上JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪种都继承自Queue。
一、ConcurrentLinkedQueue
是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLikedQueue性能好于BlockingQueue。
它是一个基于连接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。
ConcurrentLinkedQueue重要方法:
add()和offer()都是加入元素的方法(在ConcurrentLinkedQueue中,这两个方法没有任何区别)。
poll()和peek()都是取头元素节点,区别在于前者会删除元素,后者不会。
下面看一个例子:
/*
* 一个基于链接节点的、无界的、线程安全的队列。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部
* 是队列中时间最短的元素。新的元素插入到队列的尾部,队列检索操作从队列头部获得元素。当许多线程共享访问一个公共 collection
* 时,ConcurrentLinkedQueue 是一个恰当的选择。此队列不允许 null 元素。
*/
private void concurrentLinkedQueueTest() {
ConcurrentLinkedQueue concurrentLinkedQueue = new ConcurrentLinkedQueue();
concurrentLinkedQueue.add("a");
concurrentLinkedQueue.add("b");
concurrentLinkedQueue.add("c");
concurrentLinkedQueue.offer("d"); // 将指定元素插入到此队列的尾部。
concurrentLinkedQueue.peek(); // 检索并移除此队列的头,如果此队列为空,则返回 null。
concurrentLinkedQueue.poll(); // 检索并移除此队列的头,如果此队列为空,则返回 null。
for (String str : concurrentLinkedQueue) {
System.out.println(str);
}
}
注意:ConcurrentLinkedQueue的API.size() 是要遍历一遍集合的,速很慢,所以判空时,尽量要避免用size(),而改用isEmpty()。
二、BlockingQueue接口
ArrayBlockingQueue:基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费不能完全并行,长度是需要定义的,可以指定先进先出或者先进后出,也叫有界队列,在很多场合非常适合使用。
package concurrent;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class ArrayBlockingQueueTest {
private ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(10);
public static void main(String[] args) throws InterruptedException {
final ArrayBlockingQueueTest arrayBlockingQueueTest = new ArrayBlockingQueueTest();
new Thread(new Runnable() {
public void run() {
try {
arrayBlockingQueueTest.producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
try {
arrayBlockingQueueTest.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
private void producer() throws InterruptedException {
for(int i=0; i<100; i++) {
System.out.println("arrayBlockingQueue.size()="+arrayBlockingQueue.size());
//Thread.sleep(1000);
//队列满了之后会直接抛出异常
//arrayBlockingQueue.add(i);
//队列满了之后会等待队列腾出空间
//arrayBlockingQueue.put(i);
//将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
arrayBlockingQueue.offer(i);
}
}
private void consumer() throws InterruptedException {
while(true) {
//Thread.sleep(1000);
//获取并移除此队列的头部,在指定的等待时间前等待可用的元素。如果已经没有可用的元素,则没10s返回一个null
// System.out.println(arrayBlockingQueue.poll(10000, TimeUnit.MILLISECONDS));
//获取并移除此队列的头部,在元素变得可用之前一直等待
System.out.println(arrayBlockingQueue.take());
//获取但不移除此队列的头;如果此队列为空,则返回 null
//System.out.println(arrayBlockingQueue.peek());
}
}
}
关于队列中各方法的说明:
操作 | 抛出异常 | 返回个特殊值 | 阻塞到队列可用 | 一定时间后退出 | 操作方式 |
添加元素 | add(e) | offer(e) | put(e) | offer(e,time,unit) | 添加到队尾 |
移除元素 | remove() | poll() | take() | poll(e,time,unit) | 获取头元素并移除 |
查询元素 | element() | peek() | 无 | 无 | 获取头元素不移除 |
LinkedBlockingQueue:基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也是维护着一个数据缓冲队列(该队列有一个链表构成),LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作的完全并行运行。它是一个无界队列。
private LinkedBlockingQueue queue;//礼物的队列
private final static int GET_QUEUE_GIFT = 0;//从队列中获取礼物
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case GET_QUEUE_GIFT://如果是从队列中获取礼物实体的消息
if (!queue.isEmpty()) {
String vo = queue.poll();
if (vo != null) {//如果从队列中获取的礼物不为空,那么就将礼物展示在界面上
Log.e("------", "------获取的------" + vo);
handler.sendEmptyMessageDelayed(GET_QUEUE_GIFT, 1000);
}
} else {//如果这次从队列中获取的消息是礼物是空的,则一秒之后重新获取
Log.e("------", "------获取的------isEmpty");
}
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.addqueue).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 0; i < 6; i++) {
queue.add("我是队列中的-----第" + (i + 6) + "个");
}
handler.sendEmptyMessageDelayed(GET_QUEUE_GIFT, 1000);//轮询队列获取礼物
}
});
queue = new LinkedBlockingQueue<>();
for (int i = 0; i < 6; i++) {
queue.add("我是队列中的第" + i + "个");
}
handler.sendEmptyMessageDelayed(GET_QUEUE_GIFT, 1000);//轮询队列获取礼物
}
PriorityBlockingQueue:基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口),在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,他也是一个无界的队列。add()并不进行排序操作,只有在取数据时才进行排序。
public static PriorityBlockingQueue queue = new PriorityBlockingQueue();
public static void main(String[] args) {
queue.add(new User(1,"wu"));
queue.add(new User(5,"wu5"));
queue.add(new User(23,"wu23"));
queue.add(new User(55,"wu55"));
queue.add(new User(9,"wu9"));
queue.add(new User(3,"wu3"));
for (User user : queue) {
try {
System.out.println(queue.take().name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//静态内部类
static class User implements Comparable{
public User(int age,String name) {
this.age = age;
this.name = name;
}
int age;
String name;
@Override
public int compareTo(User o) {
return this.age > o.age ? -1 : 1;
}
}
DelayQueue:带有延迟时间的queue,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue中的元素必须实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayQueueTest {
private static DelayQueue delayQueue = new DelayQueue();
private static long count = 0L;
private static final int taskNum = 4;
public static void main(String[] args) throws InterruptedException {
Object num = new Object();
final DelayQueueTest delayQueueTest = new DelayQueueTest();
new Thread(new Runnable() {
public void run() {
try {
delayQueueTest.producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
while(true) {
if(delayQueue.size()==taskNum) {
break;
}
}
new Thread(new Runnable() {
public void run() {
try {
delayQueueTest.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
try {
delayQueueTest.count();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
private void count() throws InterruptedException {
while(true) {
Thread.sleep(1000);
count++;
System.out.println("时间值="+count);
if(taskNum==count) {
break;
}
}
}
private void producer() throws InterruptedException {
for(int i=0; i implements Delayed{
private String key;
private T item;
private long liveTime;
private long removeTime;
public DelayedItem(String key,T item,long liveTime) {
this.key = key;
this.item = item;
this.liveTime = liveTime;
this.removeTime = liveTime;
}
/**
* 当返回值小于等于0时则缓存时间到达,take将取出元素
* @param unit
* @return
*/
public long getDelay(TimeUnit unit) {
return removeTime-count;
}
public int compareTo(Delayed o) {
if(o instanceof DelayedItem) {
//已经在队列中存在的对象
DelayedItem tmpDelayedItem = (DelayedItem)o;
//System.out.println("比较对象==="+tmpDelayedItem.key+"==="+this.key);
//失效时间越长的排到队尾
if(this.removeTime > tmpDelayedItem.removeTime) {
return 1;
} else if(this.removeTime == tmpDelayedItem.removeTime) {
return 0;
} else {
return -1;
}
}
return -1;
}
@Override
public String toString() {
return "DelayedItem{" +
"key='" + key + '\'' +
", item=" + item +
", liveTime=" + liveTime +
", removeTime=" + removeTime +
'}';
}
}
}
运行结果:
生产者=DelayedItem{key='0', item=0, liveTime=1, removeTime=1}
生产者=DelayedItem{key='1', item=1, liveTime=2, removeTime=2}
生产者=DelayedItem{key='2', item=2, liveTime=3, removeTime=3}
生产者=DelayedItem{key='3', item=3, liveTime=4, removeTime=4}
时间值=1
消费者=DelayedItem{key='0', item=0, liveTime=1, removeTime=1}
时间值=1
时间值=2
消费者=DelayedItem{key='1', item=1, liveTime=2, removeTime=2}
时间值=1
时间值=2
时间值=3
消费者=DelayedItem{key='2', item=2, liveTime=3, removeTime=3}
时间值=1
时间值=2
时间值=3
时间值=4
消费者=DelayedItem{key='3', item=3, liveTime=4, removeTime=4}
SynchronousQueue:一种没有缓冲的队列,生产者产生的数据直接被消费者获取并消费。一个没有容量的并发队列有什么用了?或者说存在的意义是什么?SynchronousQueue 的实现非常复杂,SynchronousQueue 内部没有容量,但是由于一个插入操作总是对应一个移除操作,反过来同样需要满足。那么一个元素就不会再SynchronousQueue 里面长时间停留,一旦有了插入线程和移除线程,元素很快就从插入线程移交给移除线程。也就是说这更像是一种信道(管道),资源从一个方向快速传递到另一方 向。需要特别说明的是,尽管元素在SynchronousQueue 内部不会“停留”,但是并不意味之SynchronousQueue 内部没有队列。实际上SynchronousQueue 维护者线程队列,也就是插入线程或者移除线程在不同时存在的时候就会有线程队列。既然有队列,同样就有公平性和非公平性特性,公平性保证正在等待的插入线 程或者移除线程以FIFO的顺序传递资源。显然这是一种快速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入着(生产者)传递给移除着(消费者),这在多任务队列中是最快处理任务的方式。在线程池的相关章节中还会更多的提到此特性。
它模拟的功能类似于生活中一手交钱一手交货这种情形,像那种货到付款或者先付款后发货模型不适合使用SynchronousQueue。首先要知道SynchronousQueue没有容纳元素的能力,即它的isEmpty()方法总是返回true,但是给人的感觉却像是只能容纳一个元素。
import java.util.Random;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueTest {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue queue = new SynchronousQueue();
new Product(queue).start();
new Customer(queue).start();
}
static class Product extends Thread{
SynchronousQueue queue;
public Product(SynchronousQueue queue){
this.queue = queue;
}
@Override
public void run(){
while(true){
int rand = new Random().nextInt(1000);
System.out.println("生产了一个产品:"+rand);
System.out.println("等待三秒后运送出去...");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
queue.put(rand);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(queue.isEmpty());
}
}
}
static class Customer extends Thread{
SynchronousQueue queue;
public Customer(SynchronousQueue queue){
this.queue = queue;
}
@Override
public void run(){
while(true){
try {
System.out.println("消费了一个产品:"+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------------------------------------------");
}
}
}
}
运行结果:
生产了一个产品:542
等待三秒后运送出去...
true
消费了一个产品:542
生产了一个产品:183
等待三秒后运送出去...
------------------------------------------
true
消费了一个产品:183
------------------------------------------
生产了一个产品:583
等待三秒后运送出去...
true
消费了一个产品:583
------------------------------------------