一个关于多线程池任务配合的项目的总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011567806/article/details/50148651

 11月接了两个小项目的开发任务,时间有点紧,代码还有很多可以优化的地方

项目一

需求,纯后台项目,开发一个自动功能每日定时获取数据库里前一天某些数据,发送到外部系统交互,得到返回结果之后将结果记录回数据库。

项目选用框架

Spring集成quarz来完成定时任务一直是我第一选择。数据库框架,选用了mybatis进行集成,虽然我一直偏爱jdbcTemplate和JPA。

 

技术处理重点:多线程处理和线程池配合

具体业务平均每一小时有大概两万多条的数据需要进行发送到外部系统进行交互,交互完毕之后,更新数据。但是,因为每条数据里都有大字段,造成了每次查询和更新都比较慢,单线程的话,操作下来大概时间在两个小时左右。故想换成多线程。

 

处理的数据流程详细为这么一个流程:1从数据库中扫描出数据2将数据发送到外部系统处理3将处理的结果更新到数据库。

性能卡在第2步,因为使用外网交互,网络交互消耗了很多时间。

现在的想法是,因为发送外部系统耗时比较久,更新数据库耗时时间比较短,

故,想将这两个步骤分开,使用两个线程池进行分别操作,

考虑了两种方法进行操作,将123作为一个整体任务,由一个有多条分发线程的线程池进行任务的分发,每一个整体任务作为一条工作线程进行真正的工作。这种做法比较简单,也比较容易完成。但是我考虑的是,如果因为第2步耗时的原因,会导致整个流程停滞,这个不是我想要的。

于是我考虑的是第二种做法,将1作为一个整体,作为一个任务,将23作为一个单独的任务,进行单独操作。(1任务为生产者放入线程池1,23为消费者放入线程池2)

 

如果基于这种做法,那么接下来的需要考虑的就是如何存放生产者得到的数据,最开始考虑的是ConcurrentHashMap,但是,存在着没有办法在两个线程池中传递key的问题,很可能第二个线程池运行的时候,第一个线程池还没有把数据放入currentMap中。

最后选用的是采用缓存的形式,选用了ConcurrentLinkedQueue 来offer进数据,而生产者到这个Queue来进行poll。但是这样又会有一个问题了,如何判断队列中的任务是否被执行完。为什么会有这个问题呢,

线程池1每60分钟从数据库里面获取数据,然互offer进队列

线程池2每10分钟从队列中poll出数据来,外部系统交互,更改数据库数据的标记位。

现在的问题是,2执行效率很慢,10*6次没有办法将队列中的任务全部执行完,也就是其实任务是压到了线程池的阻塞队列中,而没有执行(没有更改标记位),而1的定时时间又到了,造成了重复获取了数据库的数据。所以我想判断2中的阻塞队列中的任务是否被执行完,如果执行完了,就让1去获取数据,如果没有执行完,就不让1去获取重复的数据。

但是,要判断队列中的任务是否被完全执行是不可能的。。比如,用队列的长度去判断。用队列的长度作为判断是不准确的,因为有可能我的数据已经被扫到任务里了,但是工作线程没启动,所以,还没被压进队列。如下代码:

 ArrayList<SignatureData>customers= (ArrayList<SignatureData>) signedDataMapper.getSignatureDataToTSP(hashMap);

       int count = customers.size();

       for(int i=0;i<count;i++){

         TSPPOOL.execute(newTSPThread(customers.get(i),countDownLatch));

       }


其中signedDataMapper.getSignatureDataToTSP(hashMap);这一步,将数据库中的所有需要扫描的数据进行了扫描,但是还没有offer进队列,如果这个时候消费者线程池去判断队列的长度为0,就会认为是没有任务需要执行,这是不对的。

那么接下来 问题就很明确了,我需要线程池1完成操作之后,线程池2才能进行操作。这时候,我使用了一个很好用的工具CountDownLatch。通过它的计数。就可以完成这个工作。为了保险,我还在缓存中加入了一个变量,启动生产者线程池的定时器线程判断这个值是否为0,如果不为0,是因为上一次的认为还没被消费完,就不执生产了。为0的话,执行生产,将生产的总数设置进去。启动消费者线程池的定时器线程去判断这个数是否为0,为0则不去执行,不为0则执行取到的这个值,执行完之后,消费者线程池通过CountDownLatch这个工具判读执行完这个值的次数之后,再将这个值设置为0。到这里,两个线程池的配合就准确无误了。

最终代码

生产者:

if((CacheUtil.INSTANCE.getCountTasks()!=0)){
    System.out.println("还有未处理完的数据,延迟扫描数据");
    return;
    }

       ArrayList<SignatureData>customers = (ArrayList<SignatureData>) signedDataMapper.getSignatureDataToTSP(hashMap);
       int count = customers.size();
       CountDownLatch countDownLatch = new CountDownLatch(count);
       for(int i=0;i<count;i++){
         TSPPOOL.execute(new TSPThread(customers.get(i),countDownLatch));
       }
       countDownLatch.await();
       CacheUtil.INSTANCE.setCountTasks(count);

消费者

if(CacheUtil.INSTANCE.getCountTasks()==0){

return;
}
System.out.println("更新数据前有数据"+CacheUtil.INSTANCE.getDatas().size()+"条");
CountDownLatch countDownLatch = new CountDownLatch(CacheUtil.INSTANCE.getCountTasks());
while(!CacheUtil.INSTANCE.getDatas().isEmpty()){
updatePool.execute(new UpdateThread(CacheUtil.INSTANCE.getDatas().poll(),countDownLatch));
}
System.out.println("更新数据后有数据"+CacheUtil.INSTANCE.getDatas().size()+"条");
countDownLatch.await();
CacheUtil.INSTANCE.setCountTasks(0);

附:mybatis对blob的处理相当简单,实体类用byte[]去接收,mapper.xml文件中配置为:#{xx,jdbcType=BLOB}。



阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页