JAVA队列的使用
JAVA队列的使用
今天跟大家来看看如何在项目中使用队列。首先我们要知道使用队列的目的是什么?一般情况下,如果是一些及时消息的处理,并且处理时间很短的情况下是不需要使用队列的,直接阻塞式的方法调用就可以了。但是,如果在消息处理的时候特别费时间,这个时候如果有新的消息来了,就只能处于阻塞状态,造成用户等待。这个时候在项目中引入队列是十分有必要的。当我们接受到消息后,先把消息放到队列中,然后再用新的线程进行处理,这个时候就不会有消息的阻塞了。下面就跟大家介绍两种队列的使用,一种是基于内存的,一种是基于数据库的。
首先,我们来看看基于内存的队列。在Java的并发包中已经提供了BlockingQueue的实现,比较常用的有ArrayBlockingQueue和LinkedBlockingQueue,前者是以数组的形式存储,后者是以Node节点的链表形式存储。至于数组和链表的区别这里就不多说了。
BlockingQueue 队列常用的操作方法:
1.往队列中添加元素: add(), put(), offer()
2.从队列中取出或者删除元素: remove() element() peek() pool() take()
每个方法的说明如下:
offer()方法往队列添加元素如果队列已满直接返回false,队列未满则直接插入并返回true;
add()方法是对offer()方法的简单封装.如果队列已满,抛出异常new IllegalStateException("Queue full");
put()方法往队列里插入元素,如果队列已经满,则会一直等待直到队列为空插入新元素,或者线程被中断抛出异常.
remove()方法直接删除队头的元素:
peek()方法直接取出队头的元素,并不删除.
element()方法对peek方法进行简单封装,如果队头元素存在则取出并不删除,如果不存在抛出异常NoSuchElementException()
pool()方法取出并删除队头的元素,当队列为空,返回null;
take()方法取出并删除队头的元素,当队列为空,则会一直等待直到队列有新元素可以取出,或者线程被中断抛出异常
offer()方法一般跟pool()方法相对应, put()方法一般跟take()方法相对应.日常开发过程中offer()与pool()方法用的相对比较频繁.
下面用一个例子来看看是怎么使用的。
- import java.util.concurrent.BlockingQueue;
- import java.util.concurrent.Executors;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ScheduledExecutorService;
- import java.util.concurrent.TimeUnit;
- public class UserTask {
- //队列大小
- private final int QUEUE_LENGTH = 10000*10;
- //基于内存的阻塞队列
- private BlockingQueue<String> queue = new LinkedBlockingQueue<String>(QUEUE_LENGTH);
- //创建计划任务执行器
- private ScheduledExecutorService es = Executors.newScheduledThreadPool(1);
- /**
- * 构造函数,执行execute方法
- */
- public UserTask() {
- execute();
- }
- /**
- * 添加信息至队列中
- * @param content
- */
- public void addQueue(String content) {
- queue.add(content);
- }
- /**
- * 初始化执行
- */
- public void execute() {
- //每一分钟执行一次
- es.scheduleWithFixedDelay(new Runnable(){
- public void run() {
- try {
- String content = queue.take();
- //处理队列中的信息。。。。。
- System.out.println(content);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }, 0, 1, TimeUnit.MINUTES);
- }
- }
以上呢,就是基于内存的队列的介绍,基于内存的队列,队列的大小依赖于JVM内存的大小,一般如果是内存占用不大且处理相对较为及时的都可以采用此种方法。如果你在队列处理的时候需要有失败重试机制,那么用此种队列就不是特别合适了。下面就说说基于数据库的队列。
基于数据库的队列,很好理解,就是接收到消息之后,把消息存入数据库中,设置消费时间、重试次数等,再用新的线程从数据库中读取信息,进行处理。首先来看看数据库的设计。
字段
|
类型
|
说明 |
queue_id
|
bigint
|
队列ID,唯一标识
|
create_time
|
bigint
|
创建时间
|
type
|
int
|
业务类型 |
status
|
int
|
处理状态位 : 1:有效可处理(active) 3:临时被占用 (locked) 5:处理完毕 标记删除(deleted)
|
consume_status
|
int
|
消费状态:1:未消费 2:消费成功 3:消费失败,等待下次消费 4:作废
|
update_time
|
bigint
|
更新时间
|
locker
|
varchar
|
占用标签
|
last_consume_time
|
bigint
|
最后一次消费时间
|
next_consume_time
|
bigint
|
可消费开始时间
|
consume_count
|
int |
消费次数
|
json_data
|
text
|
数据信息 json格式
|
代码示例如下:
- /**
- * 批量获取 可以消费的消息
- * 先使用一个时间戳将被消费的消息锁定,然后再使用这个时间戳去查询锁定的数据。
- * @param count
- * @return
- */
- public List<Queue> findActiveQueueNew(int count) {
- //先去更新数据
- String locker = String.valueOf(System.currentTimeMillis())+random.nextInt(10000);
- int lockCount = 0;
- try {
- //将status为1的更新为3,设置locker,先锁定消息
- lockCount = queueDAO.updateActiveQueue(PayConstants.QUEUE_STATUS_LOCKED,
- PayConstants.QUEUE_STATUS_ACTIVE, count, locker);
- } catch (Exception e) {
- logger.error(
- "QueueDomainRepository.findActiveQueueNew error occured!"
- + e.getMessage(), e);
- throw new TuanRuntimeException(
- PayConstants.SERVICE_DATABASE_FALIURE,
- "QueueDomainRepository.findActiveQueue error occured!", e);
- }
- //如果锁定的数量为0,则无需再去查询
- if(lockCount == 0){
- return null;
- }
- //休息一会在再询,防止数据已经被更改
- try {
- Thread.sleep(1);
- } catch (Exception e) {
- logger.error("QueueDomainRepository.findActiveQueue error sleep occured!"
- + e.getMessage(), e);
- }
- List<Queue> activeList = null;
- try {
- activeList = queueDAO.getByLocker(locker);
- } catch (Exception e) {
- logger.error("QueueDomainRepository.findActiveQueue error occured!"
- + e.getMessage(), e);
- throw new TuanRuntimeException(
- PayConstants.SERVICE_DATABASE_FALIURE,
- "QueueDomainRepository.findActiveQueue error occured!",e);
- }
- return activeList;
- }
获取到消息之后,还需要再判断消息是否合法,如是否达到最大消费次数,消息是否已被成功消费,等,判断代码如下:
- /**
- * 验证队列modle 的合法性
- *
- * @param model
- * @return boolean true,消息还可以消费。false,消息不允许消费。
- */
- public boolean validateQueue(final QueueModel model){
- int consumeCount = model.getConsumeCount();
- if (consumeCount >= PayConstants.QUEUE_MAX_CONSUME_COUNT) {
- //消费次数超过了最大次数
- return false;
- }
- int consumeStatus = model.getConsumeStatus();
- if(consumeStatus == PayConstants.QUEUE_STATUS_CONSUMER_SUCCESS){
- //消息已经被成功消费
- return false;
- }
- QueueStatusEnum queueStatusEnum = model.getQueueStatusEnum();
- if(queueStatusEnum == null || queueStatusEnum != QueueStatusEnum.LOCKED){
- //消息状态不正确
- return false;
- }
- String jsonData = model.getJsonData();
- if(StringUtils.isEmpty(jsonData)){
- //消息体为空
- return false;
- }
- return true;
- }
消息处理完毕之后,根据消费结果修改数据库中的状态。
- public void consume(boolean isDelete, Long consumeMinTime,
- String tradeNo,int consumeCount) {
- QueueDO queueDO = new QueueDO();
- if (!isDelete) {
- //已经到了做大消费次数,消息作废 不再处理
- if (consumeCount >= PayConstants.QUEUE_MAX_CONSUME_COUNT) {
- //达到最大消费次数的也设置为消费成功
- queueDO.setConsumeStatus(PayConstants.QUEUE_STATUS_CONSUMER_SUCCESS);
- queueDO.setStatus(PayConstants.QUEUE_STATUS_CANCEL);
- } else {
- queueDO.setConsumeStatus(PayConstants.QUEUE_STATUS_CONSUMER_FAILED);
- //设置为可用状态等待下次继续发送
- queueDO.setStatus(PayConstants.QUEUE_STATUS_ACTIVE);
- }
- } else {
- //第三方消费成功
- queueDO.setConsumeStatus(PayConstants.QUEUE_STATUS_CONSUMER_SUCCESS);
- queueDO.setStatus(PayConstants.QUEUE_STATUS_DELETED);
- }
- queueDO.setNextConsumeTime(consumeMinTime == null ? QueueRuleUtil
- .getNextConsumeTime(consumeCount) : consumeMinTime);
- if (StringUtils.isNotBlank(tradeNo)) {
- queueDO.setTradeNo(tradeNo);
- }
- long now = System.currentTimeMillis();
- queueDO.setUpdateTime(now);
- queueDO.setLastConsumeTime(now);
- queueDO.setConsumeCount(consumeCount);
- queueDO.setQueueID(id);
- setQueueDOUpdate(queueDO);
- }
下次消费时间的计算如下:根据消费次数计算,每次消费存在递增的时间间隔。
- /**
- * 队列消费 开始时间 控制
- */
- public class QueueRuleUtil {
- public static long getNextConsumeTime(int consumeCount) {
- return getNextConsumeTime(consumeCount, 0);
- }
- public static long getNextConsumeSecond(int consumeCount) {
- return getNextConsumeTime(consumeCount, 0);
- }
- public static long getNextConsumeTime(int cousumeCount, int addInteval) {
- int secends = getNextConsumeSecond(cousumeCount,addInteval);
- return System.currentTimeMillis()+secends*1000;
- }
- public static int getNextConsumeSecond(int cousumeCount, int addInteval) {
- if (cousumeCount == 1) {
- return addInteval + 10;
- } else if (cousumeCount == 2) {
- return addInteval + 60;
- } else if (cousumeCount == 3) {
- return addInteval + 60 * 5;
- } else if (cousumeCount == 4) {
- return addInteval + 60 * 15;
- } else if (cousumeCount == 5) {
- return addInteval + 60 * 60;
- } else if (cousumeCount == 6){
- return addInteval + 60 * 60 *2;
- } else if(cousumeCount == 7){
- return addInteval + 60 * 60 *5;
- } else {
- return addInteval + 60 * 60 * 10;
- }
- }
除此之外,对于消费完成,等待删除的消息,可以将消息直接删除或者是进行备份。最好不要在该表中保留太多需要删除的消息,以免影响数据库的查询效率。
我们在处理消息的时候,首先对消息进行了锁定,设置了locker,如果系统出现异常的时候,也会产生消息一直处于被锁定的状态,此时可能还需要定期去修复被锁定的消息。
- /**
- * 批量获取 可以消费的消息
- *
- * @param count
- * @return
- */
- public void repairQueueByStatus(int status) {
- List<QueueDO> activeList = null;
- try {
- Map<String,Object> params = new HashMap<String,Object>();
- params.put("status", status);
- //下次消费时间在当前时间3小时以内的消息
- params.put("next_consume_time", System.currentTimeMillis()+3*60*1000);
- activeList = queueDAO.findQueueByParams(params);
- } catch (Exception e) {
- logger.error("QueueDomainRepository.repairQueueByStatus find error occured!"
- + e.getMessage(), e);
- throw new TuanRuntimeException(
- PayConstants.SERVICE_DATABASE_FALIURE,
- "QueueDomainRepository.findQueueByStatus error occured!",e);
- }
- if (activeList == null || activeList.size() == 0) {
- return ;
- }
- for (QueueDO temp : activeList) {
- try {
- //status=1,可被消费
- queueDAO.update(temp.getQueueID(), PayConstants.QUEUE_STATUS_ACTIVE);
- } catch (Exception e) {
- logger.error("QueueDomainRepository.repairQueueByStatus update error occured!"
- + e.getMessage(), e);
- throw new TuanRuntimeException(
- PayConstants.SERVICE_DATABASE_FALIURE,
- "QueueDomainRepository.repairQueueByStatus update error occured!",e);
- }
- }
- }
以上就是对两种队列的简单说明。在使用基于数据库的队列的时候,其中还使用到了事件处理机制,这部分的内容,就下次的时候再去介绍。
JAVA队列的使用的更多相关文章
- java队列Queue及阻塞队列
java队列 接口Queue类在java.util包,定义了以下6个方法 详细查看官方文档https://docs.oracle.com/javase/7/docs/api/java/util/Que ...
- Java:Java 队列的遍历
Java队列到底有没有可以遍历的功能呢?暂且试一下吧 参考链接:stl容器遍历测试 1.LinkedList实现简单遍历 for(Iter =LocTimesSerials.size()-1; iSe ...
- Java队列集合的性能测试
同时开10个线程存入和取出100万的数据,结论如下: DoubleBufferedQueue < ConcurrentLinkedQueue < ArrayBlockingQueue &l ...
- java队列——queue详细分析
Queue: 基本上,一个队列就是一个先入先出(FIFO)的数据结构 Queue接口与List.Set同一级别,都是继承了Collection接口.LinkedList实现了Deque接 口. Q ...
- java队列
"队列"这个单词是英国人说的"排".在英国"排队"的意思就是站到一排当中去.计算机科学中,队列是一种数据结构,有点类似栈,只是在队列中第一个 ...
- Java队列学习
队列是Java集合中的重要组成部分,具有先进先出的特性,使其具有广泛的应用场景,比如排队等.因此今天就来学习一下Java中的队列.本文的例子使用的Java8环境. 继承类图 学习队列,首先要知道它的类 ...
- Java队列Queue
上个星期总结了一下synchronized相关的知识,这次将Queue相关的知识总结一下,和朋友们分享. 在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列.Java提供 ...
- Java队列——线程池创建的例子
线程池为线程生命周期开销问题和资源不足问题提供了解决方案.通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上.其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟.这 ...
- Java队列——Disruptor 的使用
.什么是 Disruptor 从功能上来看,Disruptor 是实现了“队列”的功能,而且是一个有界队列.那么它的应用场景自然就是“生产者-消费者”模型的应用场合了. 可以拿 JDK 的 Bloc ...
随机推荐
- [Cubieboard] 在 Cubieboard 上安装 Node.js 和 npm
你有两个选择可以实现在Cubieboard上安装NodeJS,下载别人已经编译完成适用于Cubieboard的NodeJS二进制包,或者自己下载源码自行在Cubieboard上进行编译. 使用编译完成 ...
- DOM的学习
今天学习了DOM,感觉学习起来真的没那么简单啦,这不是一个好现象啊,只有依靠自己大补课,嘿嘿,具体的总结了一下,今天学习的其实并不多,仅仅学习了不同的节点类型,但是知识还是蛮碎的,要一点一点的总结,昨 ...
- react中的hoc和修饰器@connect结合使用
在学习react-redux的时候,看到了修饰器这个新的属性,这个是es7的提案属性,很方便.于是我用@connect代替了connect(使用的时候需要配置,这里不赘述),省去了很多不必要的代码,但 ...
- 微信redirect_uri 回调错误,scope权限错误
scope权限错误以及微信redirect_uri回调错误 昨天修改项目的时候,初始时,因为项目最开始使用的是第三方授权处理,拿到的用户openid是第三方账号的,所以需要将获取对方信息的代码修改.只 ...
- jTemplates
jTemplates是一个基于JQuery的模板引擎插件,功能强大,有了他你就再不用为使用JS绑定数据集时发愁了. 首先送上jTtemplates的官网地址:http://jtemplates.tpy ...
- vue - 父组件数据变化控制子组件类名切换
先说当时的思路和实现核心是父子组件传值和v-bind指令动态绑定class实现 1. 父组件引用.注册.调用子组件script中引用 import child from '../components/ ...
- 原生js--http请求
1.终止请求和超时 终止请求XMLHttpRequest对象提供abort方法,调用该方法时触发abort事件 XHR2提供了timeout属性,当超时发生时触发timeout事件.但浏览器尚不支持自 ...
- python tkinter教程-事件绑定
一个Tkinter主要跑在mainloop进程里.Events可能来自多个地方,比如按键,鼠标,或是系统事件. Tkinter提供了丰富的方法来处理这些事件.对于每一个控件Widget,你都可以为其绑 ...
- VC 测试一段程序的运行时间 精确到ms
分三个步骤 1:声明变量 LARGE_INTEGER litmp; _int64 QPart1,QPart2; double dfMinus,dfFreq, dfTim; QueryPerforman ...
- ios三张图片组合一张
- (UIImage *)addImage:(UIImage *)image1 toImage:(UIImage *)image2 { UIGraphicsBeginImageContext(imag ...