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内存的大小,一般如果是内存占用不大且处理相对较为及时的都可以采用此种方法。如果你在队列处理的时候需要有失败重试机制,那么用此种队列就不是特别合适了。下面就说说基于数据库的队列。
       基于数据库的队列,很好理解,就是接收到消息之后,把消息存入数据库中,设置消费时间、重试次数等,再用新的线程从数据库中读取信息,进行处理。首先来看看数据库的设计。

代码示例如下:

/**
	 * 批量获取 可以消费的消息
	 * 先使用一个时间戳将被消费的消息锁定,然后再使用这个时间戳去查询锁定的数据。
	 * @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;
	}

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

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);
			}
			
		}
         }

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


原文链接:https://blog.csdn.net/lzy_lizhiyang/article/details/48311925

其他资料:

通俗的解释
消息队列,顾名思义 首先是个队列。
队列的操作有入队出队

也就是你有一个程序在产生内容然后入队(生产者) 另一个程序读取内容,内容出队(消费者)

这是最最基本的概念。

我想你应该是缺乏一个使用场景。

当你不需要立即获得结果,但是并发量又不能无限大的时候,差不多就是你需要使用消息队列的时候。

比如你写日志,因为可能一个客户端有多个操作去写,又有很多个客户端,显然并发不能无穷大,于是你就需要把写日志的请求放入到消息队列里,在消费者那边依次把队列中产生的日志写到数据库里。 

          消息队列有无数开源实现,一般没必要自己实现。zmq也好rabbitmq也好甚至redis也好,找一个合适的装上用就行

就好像rdbms/nosql一样

技术都是解决问题的,消息队列解决的是将突发大量请求转换为后端能承受的队列请求,比如你的服务器一秒能处理100个订单,但秒杀活动1秒进来1000个订单,持续10秒,在后端能力无法增加的情况下,你可以用消息队列将总共10000个请求压在队列里,后台consumer按原有能力处理,100秒后处理完所有请求(而不是直接宕机丢失订单数据)

java队列——queue详细分析

java队列(Queue)用法总结

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值