关于ArrayBlockingQueue队列的一些问题

背景:最近接手一个新的应用-商机快递,主要是给用户发送营销邮件,由于不定期会有漏发的情况,所以在里面加了一些逻辑来修复这个问题,由于系统采用了多线程的方式,改之前考虑的不周全,最后会导致重发的现象。

// 创建一个阻塞队列,容量为maxThread*2 ArrayBlockingQueue blockQueue = new ArrayBlockingQueue(maxThread * 2); exec = new BizExpressThreadPoolExecutor(maxThread, maxThread, 60, TimeUnit.SECONDS, blockQueue, maxThread * 2); exec.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 该方法可以给当前的进程注册一个清理线程,当进程退出的时候,会执行线程中的代码。 Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownHook())); while (!isShutDown) { exec.execute( new BizExpressMailRun()); try { Thread.sleep(2000); } catch (InterruptedException e) { } } /** * @param corePoolSize 线程池维护线程的最少数量 * @param maximumPoolSize 线程池维护线程的最大数量 * @param keepAliveTime 线程池维护线程所允许的空闲时间 * @param unit 线程池维护线程所允许的空闲时间的单位 * @param workQueue 线程池所使用的缓冲队列 * @param semaphore */ public BizExpressThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, int semaphore){ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); available = new Semaphore(semaphore, true); }

系统初始时会实例一个ThreadPoolExecutor对象(exec)。ThreadPoolExecutor是并发包中一个提供线程池的服务,可以很容易将一个实现了Runnable接口的任务放入线程池中执行。具体的execute(Runnable)方法执行过程为:

首先判断传入的Runnable对象是否为null,如果为null直接抛出NullPointException异常。如果不为空执行下面步骤

如果当前的线程数小于配置的corePoolSize,则调用addIfUnderCorePoolSize方法进而会调用mainLock锁。

如果当前的线程数小于配置的corePoolSize并且线程处于RUNNING状态,调用addThread增加线程,

addThread方法首先创建Worker对象,然后调用threadFactory( Thread newThread(Runnable r); )创建新的线程,如果创建新的线程不为null时,将Worker对象的thread属性指向此创建出来的线程,并将此Worker对象放入到workers中,最后增加当前线程池中的线程数。

用代码描述为:

private List workers; private int count;//当前线程池中的线程数 public void addThread(Runnable r){ Worker worker=new Worker(); Thread tt=threadFactory.newThread(r); if(rr!=null){ worker.setThread(tt); } workers.add(worker); count++; }

--------------------------

while (!isShutDown) { exec.execute(new BizExpressMailRun()); try { Thread.sleep(2000); } catch (InterruptedException e) { } } 这是阻塞队列的执行入口,是一个循环过程,中间会休眠2秒,队列的长度是初始化时的corePoolSize,消费和生产无序进行。

原来的做方法是

public void doPerform() { List<BizExpressDailyDO> bizexpresses = bizExpressDailyDAO.fetchSomeBizExpressDaily( BizExpressConfig.getServerIp(), BizExpressConfig.getBuildEachFetchNum()); // 如果没有取到数据,则休眠5秒,避免反复读取数据库。 if (bizexpresses == null || bizexpresses.size() == 0) { try { Thread.currentThread().sleep(5000); // 补发发送失败邮件 doContinue(); } catch (InterruptedException e) { } return; } takeCareOf(bizexpresses); }

一个线程执行doPerform方法,会改biz_express_daily表中的记录的ip,然后再执行takeCareOf方法来发邮件,发送成功后会将记录的status字段值改掉,但是关键的是在完成status改掉之前会有一段时间。

另外的线程调用doPerform方法,此时bizexpresses 为空,进入到if分支,调用doContinue方法。

public void doContinue() { // 获取当日被选出,但未被处理过的纪录,最多取100条 List<BizExpressDailyDO> bizexpresses = bizExpressDailyDAO.fetchLastUnbuiltBizExpressDaily( BizExpressConfig.getServerIp(), BizExpressConfig.getBuildEachFetchNum()); if (bizexpresses.size() > 0) { takeCareOf(bizexpresses); } else { return; } try { Thread.sleep(5000); } catch (InterruptedException e) { } doContinue(); }

此时的bizexpresses 是有值的,而且取出来的正好是刚才第一个线程里面的值,然后同样执行takeCareOf方法发送邮件。

这样就会造成邮件的重发,当然根据线程抢占的激烈程度,会导致重发邮件的数量也不一致。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值