java 队列的使用(转载)

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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内存的大小,一般如果是内存占用不大且处理相对较为及时的都可以采用此种方法。如果你在队列处理的时候需要有失败重试机制,那么用此种队列就不是特别合适了。下面就说说基于数据库的队列。

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

代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
      * 批量获取 可以消费的消息
      * 先使用一个时间戳将被消费的消息锁定,然后再使用这个时间戳去查询锁定的数据。
      * @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; 
    

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
      * 验证队列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
    

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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); 
    

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
  * 队列消费 开始时间 控制
  */ 
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,如果系统出现异常的时候,也会产生消息一直处于被锁定的状态,此时可能还需要定期去修复被锁定的消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
      * 批量获取 可以消费的消息
     
      * @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); 
            
               
        
}

  • 6
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值