转载声明:http://blog.csdn.net/lzy_lizhiyang/article/details/48311925

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

首先,我们来看看基于内存的队列。在Java的并发包中已经提供了BlockingQueue的实现,比较常用的有ArrayBlockingQueue和LinkedBlockingQueue,前者是以数组的形式存储,后者是以Node节点的链表形式存储。至于数组和链表的区别这里就不多说了。

BlockingQueue 队列常用的操作方法:

1.往队列中添加元素: add(), put(), offer()

2.从队列中取出或者删除元素: remove() element()  peek()   poll()  take()

每个方法的说明如下:

offer()方法往队列添加元素如果队列已满直接返回false,队列未满则直接插入并返回true;

add()方法是对offer()方法的简单封装.如果队列已满,抛出异常new IllegalStateException("Queue full");

put()方法往队列里插入元素,如果队列已经满,则会一直等待直到队列为空插入新元素,或者线程被中断抛出异常.

remove()方法直接删除队头的元素:

peek()方法直接取出队头的元素,并不删除.

element()方法对peek方法进行简单封装,如果队头元素存在则取出并不删除,如果不存在抛出异常NoSuchElementException()

poll()方法取出并删除队头的元素,当队列为空,返回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内存的大小,一般如果是内存占用不大且处理相对较为及时的都可以采用此种方法。如果你在队列处理的时候需要有失败重试机制,那么用此种队列就不是特别合适了。下面就说说基于数据库的队列。

基于数据库的队列,很好理解,就是接收到消息之后,把消息存入数据库中,设置消费时间、重试次数等,再用新的线程从数据库中读取信息,进行处理。首先来看看数据库的设计。

代码示例如下:

/**
* 批量获取 可以消费的消息
* 先使用一个时间戳将被消费的消息锁定,然后再使用这个时间戳去查询锁定的数据。
* @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 队列的使用(转载)的更多相关文章

  1. Java 集合 fail-fast机制 [ 转载 ]

    Java 集合 fail-fast机制 [转载] @author chenssy 摘要:fail-fast产生原因.解决办法 在JDK的Collection中我们时常会看到类似于这样的话: 例如,Ar ...

  2. java队列Queue及阻塞队列

    java队列 接口Queue类在java.util包,定义了以下6个方法 详细查看官方文档https://docs.oracle.com/javase/7/docs/api/java/util/Que ...

  3. JAVA队列的使用

    JAVA队列的使用 今天跟大家来看看如何在项目中使用队列.首先我们要知道使用队列的目的是什么?一般情况下,如果是一些及时消息的处理,并且处理时间很短的情况下是不需要使用队列的,直接阻塞式的方法调用就可 ...

  4. JAVA强制类型转换(转载+自己的感想) - stemon

    JAVA强制类型转换(转载+自己的感想) - stemon 时间 2013-10-29 15:52:00  博客园-Java原文  http://www.cnblogs.com/stemon/p/33 ...

  5. Java:Java 队列的遍历

    Java队列到底有没有可以遍历的功能呢?暂且试一下吧 参考链接:stl容器遍历测试 1.LinkedList实现简单遍历 for(Iter =LocTimesSerials.size()-1; iSe ...

  6. 【java】Java多线程总结之线程安全队列Queue【转载】

    原文地址:https://www.cnblogs.com/java-jun-world2099/articles/10165949.html ============================= ...

  7. Java问题排查工具箱[转载]

    转载自:http://hellojava.info/?p=517 作者:阿里毕玄 问题排查除了最重要的解决思路和逻辑推导能力外,工具也是不可缺少的一部分,一个好用的工具可以事半功倍,甚至在某些情况下会 ...

  8. 如何成为java架构师(转载)

    链接:https://www.zhihu.com/question/29031276/answer/54631312 来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 1 ...

  9. java 多线程2(转载)

    http://www.cnblogs.com/DreamSea/archive/2012/01/11/JavaThread.html Ø线程的概述(Introduction) 线程是一个程序的多个执行 ...

随机推荐

  1. 怎样获取最新版的javascript文件,解决被浏览器缓存的问题

    假设有一个js文件(以jquery为例),在服务器上的URL地址为:../js/jquery.js . 当某天jquery版本更新了,用最新版的jquery文件覆盖了原来旧版的jquery文件. 这时 ...

  2. 大数据Hadoop的HA高可用架构集群部署

        1 概述 在Hadoop 2.0.0之前,一个Hadoop集群只有一个NameNode,那么NameNode就会存在单点故障的问题,幸运的是Hadoop 2.0.0之后解决了这个问题,即支持N ...

  3. Linux--ps及top、ls命令

    day8  ps系统管理命令 ps是强大的后台进程检测命令 格式:ps [options] [--help] 选项参数: 1.-a :显示所有进程,包括PID等,包括其他用户运行的程序 2.-ef:显 ...

  4. Fiddler无所不能——之测试开发攻城狮必备利器

    Fiddler无所不能——之测试开发攻城狮必备利器 1.模拟真实网络环境4g网.3g网络.2g网络.弱网.请求超时 开启弱网Rules——Performance——勾选Simulate Modem S ...

  5. HTTP1.0,HTTP1.1,HTTP2.0的主要特征对比

    HTTP1.0 是一种无状态.无连接的应用层协议. HTTP1.0规定浏览器和服务器保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器处理完成后立即断开TCP连接(无连接),服务 ...

  6. Linux入门——shell脚本常用信息小结

    1.shell中的$默认变量    $n           $1 表示第一个参数,$2 表示第二个参数 ...    $#           命令行参数的个数    $0           当前 ...

  7. 20155239 2016-2017-2 《Java程序设计》第10周学习总(2017-04-22 16:26

    教材学习 1.基本概念划分 OIS的七层协议: 应用层.表示层.会话层.运输层.网络层.数据链路层.物理层. OIS的五层协议: 应用层.运输层.网络层.数据链路层.物理层. 由下往上介绍如下: 2. ...

  8. week14课上测试

    说明 本次测试老师将所有课下测试的易错题全部重新考察了一遍,虽然是第二次做,还提前复习过,还是错了很多,回到寝室发现老师还没有结束测试,43分的我又忍不住再做了一遍. 做第二遍发现了有几个题目是蓝墨云 ...

  9. PostgreSQL的streaming replication

    磨砺技术珠矶,践行数据之道,追求卓越价值回到上一级页面: PostgreSQL集群方案相关索引页     回到顶级页面:PostgreSQL索引页[作者 高健@博客园  luckyjackgao@gm ...

  10. 任务队列和异步接口的正确打开方式(.NET Core版本)

    任务队列和异步接口的正确打开方式 什么是异步接口? Asynchronous Operations Certain types of operations might require processi ...