关于ArrayBlockingQueue队列的一些问题

来源:互联网 发布:顶尖微信数据恢复软件 编辑:程序博客网 时间:2024/05/16 12:41

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

 // 创建一个阻塞队列,容量为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方法发送邮件。

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

原创粉丝点击