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

首先,我们来看看基于内存的队列。在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. int('x', base)中的base参数

    >>> int('12', 16) 16表示'12'就是16进制数,int()要将这个16进制数转化成10进制.

  2. 很让人受教的提高php代码质量的方法

    1.不要使用相对路径 常常会看到: require_once('../../lib/some_class.php'); 该方法有很多缺点: 它首先查找指定的php包含路径, 然后查找当前目录. 因此会 ...

  3. js生成guid(唯一标识码)

    在使用postman对接口进行测试的时候,有时候接口日志会要求写入随机标识码,这里我们可以使用js来生成. // Generate four random hex digits. function S ...

  4. 新Eclipse安装与配置 【来源网络根据实际情况自己补充】

    [第一次更新:20161108:http://blog.csdn.net/vvanity/article/details/51036678] Eclipse的官网地址:http://www.eclip ...

  5. LOJ2360. 「NOIP2016」换教室【概率DP】【Floyed】【傻逼题】

    LINK 思路 先floyed出两点最短路 然后就可以直接\(dp_{i,j,0/1}\)表示前i节课选择换j节,换不换当前这一节的最小贡献 直接可以枚举上一次决策的状态计算概率进行统计就可以了 我变 ...

  6. 《selenium2 python 自动化测试实战》(18)——自动化测试模型(一)

    线性测试 已经被淘汰了:线性测试就是一个脚本完成一个场景,代码基本没有复用,每一个脚本都要从头开始写——这哪行. 模块化与类库 这个就是分模块:有点类似面系那个对象,把功能(比如登录)单独拿出来,当下 ...

  7. grpc xservice 使用

    1. 安装(此处比较简单) dep 包管理 配置环境变量 GOPATH/bin GO/bin protoc 下载并配置环境变量 2. xservice 安装 a. 预备(一些需要的依赖) mkdir ...

  8. http请求发生了两次(options请求)

    前言 自后台restful接口流行开来,请求了两次的情况(options请求)越来越普遍.笔者也在实际的项目中遇到过这种情况,做一下整理总结. 文章书写思路: 为什么发生两次请求 http的请求方式, ...

  9. 安装Zookeeper(集群版)

    一.环境介绍(3台虚拟机) IP Hostname 192.168.2.14 javaweb04 192.168.2.15 javaweb05 192.168.2.16 javaweb06 二.配置文 ...

  10. qing-automation简单入门介绍

    1.相关文档:http://www.51testing.com/html/50/category-catid-250.html 2.进行Qing automation相关操作之前,必须安装好jdk跟a ...