队列 | 场景 | 优点 | 缺点 |
ConcurrentLinkedQueue | 1非阻塞线程安全最佳queue 2对全局的集合进行操作的场景 3一个适用于高并发场景下的队列,通过无锁的方式(CAS+volatile),实现了高并发下的高性能,通常ConcurrentLinkedQueue的性能好于BlockingQueue |
原子操作效率高,无界队列 | 要特别注意到由于它的非阻塞性,并不像其他普通集合那样,获取队列的SIZE的方法并不是常量时间的花费,而是O(N)的,因此我们应该尽可能避免使用size()方法,可以考虑使用isEmpty()代替。 |
LinkedBlockingQueue | 链表无界可阻塞线程安全队列 | 支持容量限制 | 效率相对集合队列低 |
DealyQueue | 1缓存系统的设计 2定时任务调度(订单到期、限时支付) |
可阻塞,可延迟 | 基于排序队列实现,效率很低 |
ArrayBlockingQueue | 有界队列即初始化时指定的容量,就是队列最大的容量,不会出现扩容,容量满,则阻塞进队操作;容量空,则阻塞出队操作 |
可阻塞,出入队列效率高,同时能省内存 | 容量固定,不能扩容,出入队列不能同时进行,遍历元素更快 |
总结:
如果不需要阻塞队列,优先选择ConcurrentLinkedQueue;
如果需要阻塞队列,队列大小固定优先选择ArrayBlockingQueue,
队列大小不固定优先选择LinkedBlockingQueue;
如果需要对队列进行排序,选择PriorityBlockingQueue;
如果需要一个快速交换的队列,选择SynchronousQueue;
如果需要对队列中的元素进行延时操作,则选择DelayQueue
使用案列:
使用DelayQueue实现延时任务非常简单,而且简便,全部都是标准的JDK代码实现,不用引入第三方依赖(不依赖redis实现、消息队列实现等),非常的轻量级。它的缺点就是所有的操作都是基于应用内存的,一旦出现应用单点故障,可能会造成延时任务数据的丢失。如果订单并发量非常大,因为DelayQueue是无界的,订单量越大,队列内的对象就越多,可能造成OOM的风险。所以使用DelayQueue实现延时任务,只适用于任务量较小的情况。
package org.linlinjava.litemall.core.task;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.util.concurrent.DelayQueue;import java.util.concurrent.Executors;@Componentpublic class TaskService { private TaskService taskService; private DelayQueue delayQueue = new DelayQueue(); @PostConstruct private void init() { taskService = this; Executors.newSingleThreadExecutor().execute(new Runnable() { @Override public void run() { while (true) { try { Task task = delayQueue.take(); task.run(); } catch (Exception e) { e.printStackTrace(); } } } }); } public void addTask(Task task){ if(delayQueue.contains(task)){ return; } delayQueue.add(task); } public void removeTask(Task task){ delayQueue.remove(task); }}package org.linlinjava.litemall.core.task;import com.google.common.primitives.Ints;import java.time.LocalDateTime;import java.util.concurrent.Delayed;import java.util.concurrent.TimeUnit;public abstract class Task implements Delayed, Runnable{ private String id = ""; private long start = 0; public Task(String id, long delayInMilliseconds){ this.id = id; this.start = System.currentTimeMillis() + delayInMilliseconds; } public String getId() { return id; } @Override public long getDelay(TimeUnit unit) { long diff = this.start - System.currentTimeMillis(); return unit.convert(diff, TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { return Ints.saturatedCast(this.start - ((Task) o).start); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (!(o instanceof Task)) { return false; } Task t = (Task)o; return this.id.equals(t.getId()); } @Override public int hashCode() { return this.id.hashCode(); }}
package org.jeecg.modules.Thread.queue.delayed;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
* DelayQueueCase:
* 1 实现延迟获取元素的无界队列无界阻塞队列,其中添加进该队列的元素必须实现Delayed接口(指定延迟时间),
* 而且只有在延迟期满后才能从中提取元素;
* 2 DelayQueue内部使用非线程安全的优先队列(PriorityQueue)
*
* DelayQueue使用场景:
* 1缓存系统的设计:一旦能从DelayQueue中获取元素时,表示缓存有效期到了
* 2定时任务调度(订单到期、限时支付):一旦从DelayQueue中获取到任务就开始执行
* 用DelayQueue解决这种需求是比较优雅的解决方案(不优雅的解决方案就是使用定时器进行定时轮询)。
* 使用DelayQueue实现延时任务非常简单,而且简便,全部都是标准的JDK代码实现,不用引入第三方依赖(不依赖redis实现、消息队列实现等),非常的轻量级。它的缺点就是所有的操作都是基于应用内存的,
* 一旦出现应用单点故障,可能会造成延时任务数据的丢失。如果订单并发量非常大,因为DelayQueue是无界的,订单量越大,队列内的对象就越多,可能造成OOM的风险。所以使用DelayQueue实现延时任务,
* 只适用于任务量较小的情况。
*/
@Slf4j
public class DelayQueueCase {
//声明延迟阻塞队列
private static DelayQueue delayQueue=new DelayQueue<>();
public static void main(String[] args) {
//消息队列
// ExecutorService executorService = Executors.newFixedThreadPool(2);
// executorService.execute(new DelayQueueProduct());
// executorService.execute(new DelayQueueConsumer());
//缓存
// TODO Auto-generated method stub
CacheUtils cache = new CacheUtils();
cache.put("No1", "Value of No1", 1, TimeUnit.MILLISECONDS);
System.out.println(cache.get("No1"));
// Wait 1001 milliseconds
try {
TimeUnit.MILLISECONDS.sleep(1001);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// Get the value, it will be null
System.out.println(cache.get("No1"));
}
//-----------------------------------------------DelayQueue实现消息队列------------------------------------------------------------//
/**
* 生产者
*/
public static class DelayQueueProduct implements Runnable{
@SneakyThrows
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
String data="缓存数据"+i;
//设置10s后过期
MyDelay myDelay=new MyDelay(20*1000,data);
delayQueue.put(myDelay);
log.info("插入缓存:{}",data);
// TimeUnit.SECONDS.sleep(1);
}
}
}
public static class DelayQueueConsumer implements Runnable{
@SneakyThrows
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
MyDelay data=delayQueue.take();
TimeUnit.SECONDS.sleep(5);
if(null!=data){
log.info("数据过期了:{}",data);
}
}
}
}
/**
* 实体
* @param
*/
static class MyDelay implements Delayed {
long delayTime; // 延迟时间
long expire; // 过期时间
T data;
public MyDelay(long delayTime, T t) {
this.delayTime = delayTime;
// 过期时间 = 当前时间 + 延迟时间
this.expire = System.currentTimeMillis() + delayTime;
data = t;
}
/**
* 剩余时间 = 到期时间 - 当前时间
*/
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
/**
* 优先级规则:两个任务比较,时间短的优先执行
*/
@Override
public int compareTo(Delayed o) {
long f = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
int result=(f==0)?0:((f<0)?-1:1);
return result;
}
@Override
public String toString() {
return "delayTime=" + delayTime +
", expire=" + expire +
", data=" + data;
}
}
//-----------------------------------------------DelayQueue实现缓存设计------------------------------------------------------------//
public static class CacheUtils{
private DelayQueue>> queue=new DelayQueue>>();
private ConcurrentHashMap>> data=new ConcurrentHashMap>>();
public CacheUtils() {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.interrupted()){
try {
timeoutCheck();
}catch (Exception e){
e.printStackTrace();
}
}
}
},"TimeoutChecker");
//设置守护线程
t.setDaemon(true);
t.start();
}
private void timeoutCheck() throws InterruptedException {
DelayCacheBean> item = queue.take();
if (item != null) {
data.remove(item.getItem().getKey());
}
}
public void put(K key, V value, long timeout, TimeUnit unit) {
// If key already exists, remove from queue
if (data.containsKey(key)) {
queue.remove(data.get(key));
}
Cache pair = new Cache(key, value);
DelayCacheBean> item = new DelayCacheBean>(pair,TimeUnit.MILLISECONDS.convert(timeout, unit));
data.put(key, item);
queue.put(item);
}
public void remove(K key) {
queue.remove(data.remove(key));
}
public V get(K key) {
if (data.get(key) != null) {
return data.get(key).getItem().getValue();
}
return null;
}
}
/**
* 键值对数据结构
* @param
* @param
*/
public static class Cache{
private K key;
private V value;
public Cache(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
/**
* 存放cache键值对
* @param
*/
public static class DelayCacheBean implements Delayed{
private T item;
private long expire;
private long sequence;
private AtomicLong sequencer = new AtomicLong(0);
public DelayCacheBean(T item, long expire) {
this.item = item;
this.expire = System.currentTimeMillis() + expire;
this.sequence = sequencer.getAndIncrement();
}
public T getItem(){
return item;
}
@Override
public long getDelay(@NotNull TimeUnit unit) {
return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(@NotNull Delayed o) {
if (this == o)
return 0;
if (o instanceof DelayCacheBean) {
DelayCacheBean other = (DelayCacheBean) o;
long dist = this.expire - other.expire;
if (dist > 0)
return 1;
if (dist < 0)
return -1;
// If the expire time is same, compare the sequence
return this.sequence < other.sequence ? -1 : 1;
}
// If the specified object is not a instance of DelayItem, compare
// the return value of getDelay()
long dist = this.getDelay(TimeUnit.MILLISECONDS)
- o.getDelay(TimeUnit.MILLISECONDS);
return dist > 0 ? 1 : (dist < 0 ? -1 : 0);
}
}
}
package org.linlinjava.litemall.core.task;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Executors;
@Component
public class TaskService {
private TaskService taskService;
private DelayQueue delayQueue = new DelayQueue();
@PostConstruct
private void init() {
taskService = this;
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
while (true) {
try {
Task task = delayQueue.take();
task.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
public void addTask(Task task){
if(delayQueue.contains(task)){
return;
}
delayQueue.add(task);
}
public void removeTask(Task task){
delayQueue.remove(task);
}
}
package org.linlinjava.litemall.core.task;
import com.google.common.primitives.Ints;
import java.time.LocalDateTime;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public abstract class Task implements Delayed, Runnable{
private String id = "";
private long start = 0;
public Task(String id, long delayInMilliseconds){
this.id = id;
this.start = System.currentTimeMillis() + delayInMilliseconds;
}
public String getId() {
return id;
}
@Override
public long getDelay(TimeUnit unit) {
long diff = this.start - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Ints.saturatedCast(this.start - ((Task) o).start);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!(o instanceof Task)) {
return false;
}
Task t = (Task)o;
return this.id.equals(t.getId());
}
@Override
public int hashCode() {
return this.id.hashCode();
}
}
package org.jeecg.modules.Thread.queue.array;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ArrayBlockingQueue是一个阻塞式/有界的队列
* 使用场景
* 有界队列即初始化时指定的容量,就是队列最大的容量,不会出现扩容,容量满,则阻塞进队操作;容量空,则阻塞出队操作
* 队列不支持空元素
* 先进先出队列
* 入队(生产者)
* offer:队列满了丢弃。 return false;
* add :队列满了报错 throw new IllegalStateException("Queue full");
* put :阻塞。 final ReentrantLock lock = this.lock;
* 出队(消费者)
* poll :如果队列为空则返回null。 return (count == 0) ? null : dequeue();
* take :阻塞 notEmpty.await();
* remove:报错 throw new NoSuchElementException();
*/
@Slf4j
public class ArrayBlockingQueueCase {
public static void main(String[] args) {
//通过 ExecutorService 启动 3 个线程,2 两个生产者,1 个消费者
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(5);
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 最多生产 5 个数据
AtomicInteger numberOfElementsToProduce = new AtomicInteger(5);
// 2 个生产者线程
executorService.submit(new ArrayBlockingQueueProducer(arrayBlockingQueue, numberOfElementsToProduce));
executorService.submit(new ArrayBlockingQueueProducer(arrayBlockingQueue, numberOfElementsToProduce));
// 1 个消费者线程
executorService.submit(new ArrayBlockQueueConsumer(arrayBlockingQueue, numberOfElementsToProduce));
log.info("开始等待");
executorService.shutdown();
waitUntilTerminate(executorService, 10);
}
public static void waitUntilTerminate(final ExecutorService executorService, final int timeout) {
try {
log.info("等待完毕");
//当使用awaitTermination时,主线程会处于一种等待的状态,等待线程池中所有的线程都运行完毕后才继续运行。
//如果等待的时间超过指定的时间,但是线程池中的线程运行完毕,那么awaitTermination()返回true。执行分线程已结束。
//如果等待的时间超过指定的时间,但是线程池中的线程未运行完毕,那么awaitTermination()返回false。不执行分线程已结束。
//如果等待时间没有超过指定时间,等待!
if (executorService.awaitTermination(timeout, TimeUnit.SECONDS)) { //超时后直接关闭
log.info("分线程已经结束");
executorService.shutdownNow();
}
} catch (InterruptedException e) { //awaitTermination 出现中断异常也将触发关闭
executorService.shutdownNow();
}
}
/**
* 消费者
*/
public static class ArrayBlockQueueConsumer implements Runnable {
//容量
private ArrayBlockingQueue queue;
//生产者指定的数量
private AtomicInteger numberOfElenmentsToProduce;
public ArrayBlockQueueConsumer(ArrayBlockingQueue queue, AtomicInteger numberOfElenmentsToProduce) {
this.queue = queue;
this.numberOfElenmentsToProduce = numberOfElenmentsToProduce;
}
@Override
public void run() {
try {
while (!queue.isEmpty()|| numberOfElenmentsToProduce.get() >= 0) {
try {
//从队列中获取任务,并执行任务
String task = queue.take();//阻塞
log.info("thead:{},消费者 task:{},队列:{},队列容量:{}", Thread.currentThread().getName(), task,queue.isEmpty(),numberOfElenmentsToProduce.get());
//队列中数据为空,消费者线程退出,这边由bug消费者没有消费就退出了,需要等待加上numberOfElenmentsToProduce=0
if (queue.isEmpty()&&numberOfElenmentsToProduce.get()==0) {
break;
}
} catch (Exception e) {
log.error("出错了:{}", e.getMessage());
}
}
} catch (Exception e) {
log.error(this.getClass().getName().concat(". has error"), e);
}
}
}
/**
* 生产者线程向容器存入指定总量的 任务
*/
public static class ArrayBlockingQueueProducer implements Runnable {
//容量
private ArrayBlockingQueue queue;
//生产者指定的数量
private AtomicInteger numberOfElenmentsToProduce;
public ArrayBlockingQueueProducer(ArrayBlockingQueue queue, AtomicInteger numberOfElenmentsToProduce) {
this.queue = queue;
this.numberOfElenmentsToProduce = numberOfElenmentsToProduce;
}
@Override
public void run() {
try {
while (numberOfElenmentsToProduce.get() > 0) {
try {
//向队列中添加任务
String task = String.format("task_%s", numberOfElenmentsToProduce.getAndUpdate(x ->
x - 1
));
//阻塞
queue.put(task);
log.info("thead:{},生产者 task:{}", Thread.currentThread().getName(), task);
//任务为0,生产者线程退出
if (numberOfElenmentsToProduce.get() == 0) {
break;
}
} catch (Exception e) {
log.error("出错了:{}", e.getMessage());
}
}
} catch (Exception e) {
log.error(this.getClass().getName().concat(". has error"), e);
}
}
}
}