并发队列 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 ...
随机推荐
- Android_SQLite简单的增删改查
SQLite数据库,和其他的SQL数据库不同, 我们并不需要在手机上另外安装一个数据库软件,Android系统已经集成了这个数据库,我们无需像 使用其他数据库软件(Oracle,MSSQL,MySql ...
- 04-SV连接设计和测试平台
1.验证一个设计的步骤: 生成输入激励,捕捉输出响应,决定对错和进度 2.连接设计和测试平台 信号连接:SystemVerilog已经扩展了传统的reg类型,可以像wire那样用来连接模块,它的新名字 ...
- Linux网络课程学习第四天
课程内容包括:管道符.重定向与环境变量. 学习心得:个人感觉本章节还是不太好理解,尤其是对自己的基础还不是特别的扎实课余时间还是要反复的复习.
- 关于java静态存储类的一个知识点
今天在写代码的时候产生了一个很奇怪的问题:静态类里的数据在其他类中更改之后,是否会保存 然后就动手试验了一下,结果是 ·在更改数据的类中,输出数据都是更够以后的数据 ·在先执行更改数据的类之后执行第二 ...
- PP: Meta-learning framework with applications to zero-shot time-series forecasting
From: Yoshua Bengio Problem: time series forecasting. Supplementary knowledge: 1. what is meta-learn ...
- IDEA 解决Number objects are compared using '==', not 'equals()' 警告
当代码被工具标黄色高亮时,代表需要优化或重构了 == 是值相等.对于Integer这样的数据类型而言,意义是两个Integer对象的内存地址相等.也就是说如果你有两个不同的Integer的对象, 如果 ...
- Codeforces 1303E. Erase Subsequences 代码(dp 字符串压缩一维状态优化)
https://codeforces.com/contest/1303/problem/E #include<bits/stdc++.h> using namespace std; ; i ...
- tomcat常见状态码
- 远程执行本地脚本_linux
一.远程执行脚本 1.免机器指纹确认(无需填写yes/no) ssh -o StrictHostKeyChecking=no root@192.168.108.78 2.远程执行本地脚本 ssh -o ...
- 记录 Docker 的学习过程 (网络篇)
打开2个会话,分别运行以下命令 # docker run -it -P --name nginx2 nginx #-P 端口随机映射 再打开一个会话查看 运行中的容器 # docker ps -aCO ...