并发队列 ConcurrentLinkedQueue 及 BlockingQueue 接口实现的四种队列
队列是一种特殊的线性表,它只允许在表的前端(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<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<String>();
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<String> 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<User> queue = new PriorityBlockingQueue<User>();
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<User>{
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<taskNum; i++) {
DelayedItem temp = new DelayedItem(i+"",i,(i+1));
System.out.println("生产者="+temp);
delayQueue.put(temp);
}
}
private void consumer() throws InterruptedException {
while(true) {
System.out.println("消费者="+delayQueue.take());
count = 0;
}
}
static class DelayedItem<T> 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<T> tmpDelayedItem = (DelayedItem<T>)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<Integer> queue = new SynchronousQueue<Integer>();
new Product(queue).start();
new Customer(queue).start();
}
static class Product extends Thread{
SynchronousQueue<Integer> queue;
public Product(SynchronousQueue<Integer> 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<Integer> queue;
public Customer(SynchronousQueue<Integer> 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
------------------------------------------
————————————————
版权声明:本文为CSDN博主「二一点」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a78270528/article/details/79737661
并发队列 ConcurrentLinkedQueue 及 BlockingQueue 接口实现的四种队列的更多相关文章
- robot framework接口测试之二-四种常见的POST提交数据方式
写接口测试用例时,遇到以json格式提交数据时,报错,Request如下图: Response如下图: 改成form格式提交,可以正常运行,如下图: 代码如下: ------------------- ...
- Python_内置四种队列
from queue import Queue #LILO队列q = Queue() #创建队列对象q.put(0) #在队列尾部插入元素q.put(1)q.put(2)print('LILO队列', ...
- 接口Interface的四种含义
摘自<需求分析与系统设计(第3版)>第七章Q5 1. GUI——显示信息的计算机屏幕(注:其他终端) 2. API——是一套软件程序和开发工具,为应用程序提供函数调用,使程序可以访问一些级 ...
- BlockingQueue接口
BlockingQueue接口定义了一种阻塞的FIFO queue,每一个BlockingQueue都有一个容量,让容量满时往BlockingQueue中添加数据时会阻塞,当容量为空时取元素操作会阻塞 ...
- Java.util.concurrent包学习(一) BlockingQueue接口
JDK1.7 BlockingQueue<E>接口 (extends Queue<E>) 所有父接口:Collection<E>,Iterable<E> ...
- Java 几种队列区别的简单说明
前言 队列,字面意思就可以明白. 是一种线性的数据暂存与管理工具. 也可以让各种业务功能进行逐个的队列运行. 此篇博客只说明一下Java有几种队列 未阻塞和阻塞队列的区别 未阻塞: 1.未阻塞的队列在 ...
- 并发队列之:BlockingQueue和ConcurrentLinkedQueue
一.并行和并发区别: 并行:是指两者同时执行一件事.比如赛跑,两个人都在不停的往前跑: 并发:是指资源有限的情况下,两者交替轮流使用资源.比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给 ...
- [Java 基础] 并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法
reference : http://www.cnblogs.com/linjiqin/archive/2013/05/30/3108188.html 在Java多线程应用中,队列的使用率很高,多数生 ...
- 并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法
在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列(先进先出).Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQ ...
随机推荐
- Mac苹果电脑如何格式化?
一般而言,我们想要在Windows系统上实现格式化操作是非常容易的.然而在苹果电脑上,我们则需要通过launchpad下的磁盘工具来进行,相对而言比较麻烦.关于“苹果电脑怎么格式化”的问题也困扰着无数 ...
- Jungle Roads HDU - 1301 prim
#include<stdio.h> #include<string.h> #include<iostream> using namespace std; ; int ...
- sqli-labs less-5 --> less-6
盲注笔记: SQL盲注攻击:只有两种回显结果,错误和正确:无法用url查询爆出数据库的信息:回显注入语句中加入判断方式,使得回显结果为true或者false: SQL盲注分类:布尔盲注,时间盲注,报错 ...
- Linux虚拟文件系统–VFS简介
http://www.embeddedlinux.org.cn/emb-linux/file-system/201712/20-7907.html 导读 Linux中可以支持多种文件系统,而且支持各种 ...
- 【Unity|C#】基础篇(2)——栈与堆、值类型与引用类型
传送门:https://www.cnblogs.com/moonache/p/6008048.html [笔记] 图1:值类型与引用类型 存储方式 > 值类型:数据直接存在栈中 > 引用类 ...
- mybatis第二天01
MyBatis第二天01 1.高级结果映射 1.1根据视频案例,分析表之间的关系 数据模型分析 1. 明确每张表存储的信息 2. 明确每张表中关键字段(主键.外键.非空) 3. 明确数据库中表与表之间 ...
- CodeForces 1144A
原题链接:https://vjudge.net/problem/CodeForces-1144A #include<bits/stdc++.h> using namespace std; ...
- If no other git process is currently running, this probably means a git proc
原因:用SourceTree提交代码,发现这个问题.好像是因为上个进程没停止,造成文件不识别 解决:把仓库目录里的.git/index.lock文件(文件是隐藏的)删除就可以了.删除index.loc ...
- The file is absent or does not have execute permission This file is needed to run this program
tomcat下载后发现startup.sh文件启动不了 原因: 没有权限 解决方案:chmod 777 *.sh
- Wannafly Camp 2020 Day 3E 棋技哥 - 贪心,前缀和
#include <bits/stdc++.h> using namespace std; char c[505][505]; int n,m,t,a[505][505],s[505][5 ...