首先我们要知道使用队列的目的是什么?一般情况下,如果是一些及时消息的处理,并且处理时间很短的情况下是不需要使用队列的,直接阻塞式的方法调用就可以了。但是,如果在消息处理的时候特别费时间,这个时候如果有新的消息来了,就只能处于阻塞状态,造成用户等待。这个时候在项目中引入队列是十分有必要的。当我们接受到消息后,先把消息放到队列中,然后再用新的线程进行处理,这个时候就不会有消息的阻塞了。下面就跟大家介绍两种队列的使用,一种是基于内存的,一种是基于数据库的。

首先,我们来看看基于内存的队列。在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()方法用的相对比较频繁.

下面用一个例子来看看是怎么使用的。

  1. import java.util.concurrent.BlockingQueue;
  2. import java.util.concurrent.Executors;
  3. import java.util.concurrent.LinkedBlockingQueue;
  4. import java.util.concurrent.ScheduledExecutorService;
  5. import java.util.concurrent.TimeUnit;
  6. public class UserTask {
  7. //队列大小
  8. private final int QUEUE_LENGTH = 10000*10;
  9. //基于内存的阻塞队列
  10. private BlockingQueue<String> queue = new LinkedBlockingQueue<String>(QUEUE_LENGTH);
  11. //创建计划任务执行器
  12. private ScheduledExecutorService es = Executors.newScheduledThreadPool(1);
  13. /**
  14. * 构造函数,执行execute方法
  15. */
  16. public UserTask() {
  17. execute();
  18. }
  19. /**
  20. * 添加信息至队列中
  21. * @param content
  22. */
  23. public void addQueue(String content) {
  24. queue.add(content);
  25. }
  26. /**
  27. * 初始化执行
  28. */
  29. public void execute() {
  30. //每一分钟执行一次
  31. es.scheduleWithFixedDelay(new Runnable(){
  32. public void run() {
  33. try {
  34. String content = queue.take();
  35. //处理队列中的信息。。。。。
  36. System.out.println(content);
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. }, 0, 1, TimeUnit.MINUTES);
  42. }
  43. }

以上呢,就是基于内存的队列的介绍,基于内存的队列,队列的大小依赖于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格式

代码示例如下:

  1. /**
  2. * 批量获取 可以消费的消息
  3. * 先使用一个时间戳将被消费的消息锁定,然后再使用这个时间戳去查询锁定的数据。
  4. * @param count
  5. * @return
  6. */
  7. public List<Queue> findActiveQueueNew(int count) {
  8. //先去更新数据
  9. String locker = String.valueOf(System.currentTimeMillis())+random.nextInt(10000);
  10. int lockCount = 0;
  11. try {
  12. //将status为1的更新为3,设置locker,先锁定消息
  13. lockCount = queueDAO.updateActiveQueue(PayConstants.QUEUE_STATUS_LOCKED,
  14. PayConstants.QUEUE_STATUS_ACTIVE, count, locker);
  15. } catch (Exception e) {
  16. logger.error(
  17. "QueueDomainRepository.findActiveQueueNew error occured!"
  18. + e.getMessage(), e);
  19. throw new TuanRuntimeException(
  20. PayConstants.SERVICE_DATABASE_FALIURE,
  21. "QueueDomainRepository.findActiveQueue error occured!", e);
  22. }
  23. //如果锁定的数量为0,则无需再去查询
  24. if(lockCount == 0){
  25. return null;
  26. }
  27. //休息一会在再询,防止数据已经被更改
  28. try {
  29. Thread.sleep(1);
  30. } catch (Exception e) {
  31. logger.error("QueueDomainRepository.findActiveQueue error sleep occured!"
  32. + e.getMessage(), e);
  33. }
  34. List<Queue> activeList = null;
  35. try {
  36. activeList = queueDAO.getByLocker(locker);
  37. } catch (Exception e) {
  38. logger.error("QueueDomainRepository.findActiveQueue error occured!"
  39. + e.getMessage(), e);
  40. throw new TuanRuntimeException(
  41. PayConstants.SERVICE_DATABASE_FALIURE,
  42. "QueueDomainRepository.findActiveQueue error occured!",e);
  43. }
  44. return activeList;
  45. }

获取到消息之后,还需要再判断消息是否合法,如是否达到最大消费次数,消息是否已被成功消费,等,判断代码如下:

  1. /**
  2. * 验证队列modle 的合法性
  3. *
  4. * @param model
  5. * @return boolean true,消息还可以消费。false,消息不允许消费。
  6. */
  7. public boolean validateQueue(final QueueModel model){
  8. int consumeCount = model.getConsumeCount();
  9. if (consumeCount >= PayConstants.QUEUE_MAX_CONSUME_COUNT) {
  10. //消费次数超过了最大次数
  11. return false;
  12. }
  13. int consumeStatus = model.getConsumeStatus();
  14. if(consumeStatus == PayConstants.QUEUE_STATUS_CONSUMER_SUCCESS){
  15. //消息已经被成功消费
  16. return false;
  17. }
  18. QueueStatusEnum queueStatusEnum  = model.getQueueStatusEnum();
  19. if(queueStatusEnum == null || queueStatusEnum != QueueStatusEnum.LOCKED){
  20. //消息状态不正确
  21. return false;
  22. }
  23. String jsonData = model.getJsonData();
  24. if(StringUtils.isEmpty(jsonData)){
  25. //消息体为空
  26. return false;
  27. }
  28. return true;
  29. }

消息处理完毕之后,根据消费结果修改数据库中的状态。

  1. public void consume(boolean isDelete, Long consumeMinTime,
  2. String tradeNo,int consumeCount) {
  3. QueueDO queueDO  = new QueueDO();
  4. if (!isDelete) {
  5. //已经到了做大消费次数,消息作废 不再处理
  6. if (consumeCount >= PayConstants.QUEUE_MAX_CONSUME_COUNT) {
  7. //达到最大消费次数的也设置为消费成功
  8. queueDO.setConsumeStatus(PayConstants.QUEUE_STATUS_CONSUMER_SUCCESS);
  9. queueDO.setStatus(PayConstants.QUEUE_STATUS_CANCEL);
  10. } else {
  11. queueDO.setConsumeStatus(PayConstants.QUEUE_STATUS_CONSUMER_FAILED);
  12. //设置为可用状态等待下次继续发送
  13. queueDO.setStatus(PayConstants.QUEUE_STATUS_ACTIVE);
  14. }
  15. } else {
  16. //第三方消费成功
  17. queueDO.setConsumeStatus(PayConstants.QUEUE_STATUS_CONSUMER_SUCCESS);
  18. queueDO.setStatus(PayConstants.QUEUE_STATUS_DELETED);
  19. }
  20. queueDO.setNextConsumeTime(consumeMinTime == null ? QueueRuleUtil
  21. .getNextConsumeTime(consumeCount) : consumeMinTime);
  22. if (StringUtils.isNotBlank(tradeNo)) {
  23. queueDO.setTradeNo(tradeNo);
  24. }
  25. long now = System.currentTimeMillis();
  26. queueDO.setUpdateTime(now);
  27. queueDO.setLastConsumeTime(now);
  28. queueDO.setConsumeCount(consumeCount);
  29. queueDO.setQueueID(id);
  30. setQueueDOUpdate(queueDO);
  31. }

下次消费时间的计算如下:根据消费次数计算,每次消费存在递增的时间间隔。

  1. /**
  2. * 队列消费 开始时间 控制
  3. */
  4. public class QueueRuleUtil {
  5. public static long getNextConsumeTime(int consumeCount) {
  6. return getNextConsumeTime(consumeCount, 0);
  7. }
  8. public static long getNextConsumeSecond(int consumeCount) {
  9. return getNextConsumeTime(consumeCount, 0);
  10. }
  11. public static long getNextConsumeTime(int cousumeCount, int addInteval) {
  12. int secends = getNextConsumeSecond(cousumeCount,addInteval);
  13. return System.currentTimeMillis()+secends*1000;
  14. }
  15. public static int getNextConsumeSecond(int cousumeCount, int addInteval) {
  16. if (cousumeCount == 1) {
  17. return  addInteval + 10;
  18. } else if (cousumeCount == 2) {
  19. return  addInteval + 60;
  20. } else if (cousumeCount == 3) {
  21. return  addInteval + 60 * 5;
  22. } else if (cousumeCount == 4) {
  23. return  addInteval + 60 * 15;
  24. } else if (cousumeCount == 5) {
  25. return addInteval + 60 * 60;
  26. } else if (cousumeCount == 6){
  27. return addInteval + 60 * 60 *2;
  28. } else if(cousumeCount == 7){
  29. return addInteval + 60 * 60 *5;
  30. } else {
  31. return addInteval + 60 * 60 * 10;
  32. }
  33. }

除此之外,对于消费完成,等待删除的消息,可以将消息直接删除或者是进行备份。最好不要在该表中保留太多需要删除的消息,以免影响数据库的查询效率。

我们在处理消息的时候,首先对消息进行了锁定,设置了locker,如果系统出现异常的时候,也会产生消息一直处于被锁定的状态,此时可能还需要定期去修复被锁定的消息。

  1. /**
  2. * 批量获取 可以消费的消息
  3. *
  4. * @param count
  5. * @return
  6. */
  7. public void repairQueueByStatus(int status) {
  8. List<QueueDO> activeList = null;
  9. try {
  10. Map<String,Object> params = new HashMap<String,Object>();
  11. params.put("status", status);
  12. //下次消费时间在当前时间3小时以内的消息
  13. params.put("next_consume_time", System.currentTimeMillis()+3*60*1000);
  14. activeList =  queueDAO.findQueueByParams(params);
  15. } catch (Exception e) {
  16. logger.error("QueueDomainRepository.repairQueueByStatus find error occured!"
  17. + e.getMessage(), e);
  18. throw new TuanRuntimeException(
  19. PayConstants.SERVICE_DATABASE_FALIURE,
  20. "QueueDomainRepository.findQueueByStatus error occured!",e);
  21. }
  22. if (activeList == null || activeList.size() == 0) {
  23. return ;
  24. }
  25. for (QueueDO temp : activeList) {
  26. try {
  27. //status=1,可被消费
  28. queueDAO.update(temp.getQueueID(), PayConstants.QUEUE_STATUS_ACTIVE);
  29. } catch (Exception e) {
  30. logger.error("QueueDomainRepository.repairQueueByStatus  update error occured!"
  31. + e.getMessage(), e);
  32. throw new TuanRuntimeException(
  33. PayConstants.SERVICE_DATABASE_FALIURE,
  34. "QueueDomainRepository.repairQueueByStatus update error occured!",e);
  35. }
  36. }
  37. }

以上就是对两种队列的简单说明。在使用基于数据库的队列的时候,其中还使用到了事件处理机制,这部分的内容,就下次的时候再去介绍。

Java知识总结----队列的使用的更多相关文章

  1. Java知识体系

    Java知识体系 java知识结构.jpg web框架.jpg 计算机课程体系.png 2016-08-19_090929.png 流行的哈希算法生存状况.jpg "JAVA之父" ...

  2. Android开发学习必备的java知识

    Android开发学习必备的java知识本讲内容:对象.标识符.关键字.变量.常量.字面值.基本数据类型.整数.浮点数.布尔型.字符型.赋值.注释 Java作为一门语言,必然有他的语法规则.学习编程语 ...

  3. Java知识体系纲要

    最近一段时间,把Java主要涉及到的大概念都大致学习了一遍,为了让自己能够更好地形成对Java知识体系的整体把握,先把学过的知识点添加到自己画的思维导图上. 整个Java知识体系的划分,我自己主要将它 ...

  4. Java多线程 阻塞队列和并发集合

    转载:大关的博客 Java多线程 阻塞队列和并发集合 本章主要探讨在多线程程序中与集合相关的内容.在多线程程序中,如果使用普通集合往往会造成数据错误,甚至造成程序崩溃.Java为多线程专门提供了特有的 ...

  5. 震惊!90%的程序员不知道的Java知识!

    震惊!90%的程序员不知道的Java知识! 初学Java的时候都会接触的代码 public static void main(String[] args){ ... } 当时就像背公式一样把这行代码给 ...

  6. Java 中的队列 Queue

    一.队列的定义 我们都知道队列(Queue)是一种先进先出(FIFO)的数据结构,Java中定义了java.util.Queue接口用来表示队列.Java中的Queue与List.Set属于同一个级别 ...

  7. 实现网络数据提取你需要哪些java知识

    本篇对一些常用的java知识做一个整合,三大特性.IO操作.线程处理.类集处理,目的在于能用这些只是实现一个网页爬虫的功能. Ⅰ 首先对于一个java开发的项目有一个整体性的了解认知,项目开发流程: ...

  8. Java知识回顾 (1) 编译环境与基本变量类型

    参考资料 runoob Java知识回顾序列的相关资料,主要来自 runoob,并对其中的知识进行概况或总结,去除对一个之前了解过Java的人员无关的知识点.以便能够使得一个新手,或之前有Java经验 ...

  9. Java知识集锦

    Java知识集锦 一.Java程序基础 1.1 开发和运行环境 1.2 Java语言概述 二.Java语法基础 2.1 基础类型和语法 2.2 对象和类型 2.3 包和访问控制 三.数据类型及类型转换 ...

随机推荐

  1. 安卓开发 报错 错误:This version of android studio is incompatible with the gradle version used. 的解决

    本文的解决方法主要参考以下文章: https://blog.csdn.net/sinat_15417921/article/details/51907728 Android 开发总是会遇到各种不知道怎 ...

  2. Buildroot 指定内核版本

    /******************************************************************************** * Buildroot 指定内核版本 ...

  3. MySQL 实用技巧

    概述: MySQL有许多实用的技巧,利用这些技巧能提高工作的效率,减少一些不必要的麻烦.以下是几个我在MySQL日常维护从常用的技巧. 一.prompt 命令 功能:设置mysql客户端提示符 说明: ...

  4. Ubuntu 12.04 LTS 安裝无线网卡驱动

    1,当然,首先下载得到驱动的源代码: 2,解压缩到指定位置,我就是用鼠标拖到 home 里面: 3,进入驱动所在目录 CD ~/mt7610u,(我将解压缩出来的驱动目录改名为 mt7610u 这个了 ...

  5. 日志组件logback的介绍及配置使用方法(二)

    四.Logback的默认配置 如果配置文件 logback-test.xml 和 logback.xml 都不存在,那么 logback 默认地会调用BasicConfigurator ,创建一个最小 ...

  6. git忽略已经提交的文件【转载】

    有时候我们添加.gitignore文件之前已经提交过了文件..gitignore只能忽略那些原来没有被track的文件(自添加以后,从未 add 及 commit 过的文件),如果某些文件已经被纳入了 ...

  7. Linux之 find之 ctime,atime,mtime

    在Linux操作系统中,每个文件都有很多的时间参数,其中有三个比较主要,分别是ctime,atime,mtime atime 最后一次访问时间, 如 ls, more 等, 但 chmod, chow ...

  8. 使用Navicat for Oracle新建表空间、用户及权限赋予 (转)

    Navicat for Oracle是有关Oracle数据库的客户端工具.通过这个客户端,我们可以图形方式对Oracle数据库进行操作. 说明我们此次试验的Oracle数据库版本是Oracle 10G ...

  9. POJ2564:Edit Step Ladders

    浅谈\(Trie\):https://www.cnblogs.com/AKMer/p/10444829.html 题目传送门:http://poj.org/problem?id=2564 记\(f[i ...

  10. Web 漏洞分析与防御之 CSRF(二)

    原文地址:Web 漏洞分析与防御之 CSRF(二) 博客地址:http://www.extlight.com 一.全称 跨站请求伪造(Cross-site Request Forgery) 二.原理 ...