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

首先,我们来看看基于内存的队列。在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. 【剑指offer】不用加减乘除做加法,C++实现

    原创博文,转载请注明出处! # 题目 # 思路 第一步:不考虑进位对每一位相加(异或操作) 第二步:考虑进位(位与运算+左移) 第三步:第一步和第二步相加(重复执行前两步) # 代码 #include ...

  2. 线性回归 Linear regression(3) 线性回归的概率解释

    这篇博客从一种方式推导了Linear regression 线性回归的概率解释,内容来自Standford公开课machine learning中Andrew老师的讲解. 线性回归的概率解释 在Lin ...

  3. CF1109B Sasha and One More Name

    CF1109B Sasha and One More Name 构造类题目.仔细看样例解释能发现点东西? 结论:答案只可能是 \(Impossible,1,2\) . \(Impossible:\) ...

  4. Luogu3387 缩点 【tarjan】【DP】

    Luogu3387 缩点 题目背景 缩点+DP 题目描述 给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点, ...

  5. java中线程安全问题

    在java中单线程和多线程是什么意思,他们有什么区别,分别的作用是什么? 在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”.多线程处理一个常见的例 ...

  6. {vlFeat}{matlab}{VS2010}{编译配置}

    运行程序需要vlfeat与mex等在matlab与vs2010中配置,碰到了不少困难,下面给出解决方案 1.下载vlfeat,但是vlfeat目录中并没有编译好的mex文件,需要在vs2010中编译 ...

  7. win32 去掉窗口边框

    参考:http://www.blitzbasic.com/Community/posts.php?topic=67222 Strict Graphics 320, 200 SetClsColor 0, ...

  8. 【Netty】netty学习之nio了解

    [一]五种IO模型: (1)阻塞IO(2)非阻塞IO(任务提交,工作线程处理,委托线程等待工作线程处理结果的同时,也可以做其他的事情)(3)IO复用模型.(委托线程接收多个任务,将任务提交给工作线程. ...

  9. 基于jquery 的ajax 文件下载

    ajax 文件下载,实际上就是模拟表单提交,代码如下: function download(url, data, method){ //url and data options required if ...

  10. Nunit中如何进行事务性单元测试

    单元测试要求:单元测试方法并不真正去变更数据库,也就是说单元测试不依赖于数据库中的数据.那我们如何解决执行单元测试方法后,不变更数据库中数据呢? 一般的解决方案有两种: 1. 新建一个单元测试数据库, ...