学习理解CountDownLatch

主要参考 

     https://blog.csdn.net/baidu_23086307/article/details/53141030

     https://blog.csdn.net/yanyan19880509/article/details/52349056

1. 背景

   做数据清理,有元信息表meta,从meta表中查出的数据去删除table1,table2,...tableN,之后还要删除meta表本身。

  做法:多线程,jdbc执行N条删除SQL   结果:多线程环境下,如果删除meta在删除其他表之前执行,就会导致数据清理失败甚至发生数据库死锁。

  该业务场景:必须保证meta表的删除在其他删除数据的线程执行完后最后执行

2.什么是CountDownLatch?

  CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行.

  CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。 

 伪代码如下:

 //Main thread start
  //Create CountDownLatch for N threads
  //Create and start N threads
  //Main thread wait on latch
  //N threads completes there tasks are returns
//Main thread resume execution

示意图大概如下

 

3.CountDownLatch 如何工作?

  CountDownLatch.java类中定义的构造函数:

  

      构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。

      与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。

      这样主线程的操作就会在这个方法上阻塞, 直到其他线程完成各自的任务。

       其他N 个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;

       每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。 

4. 使用例子,本人上述业务场景中使用的示例

 @DataSource(value = DataSourceTypes.performanceDbType)
    public void clearPerformanceData() {
        clearDataByTime();

        Integer beforeDay = getIptBeforeDay();
        int count = sfIptPatientdataUpdateMapper.selectCountByIntervalDay(beforeDay);
        if (count < 1) {
            return;
        }
      // 数据太大,做分批删除
        for (int i = 0; i < count / PAGE_LIMIT + 1; i++) {
            clearDataByKey(beforeDay, i * PAGE_LIMIT);
        }

    }

     private void clearDataByKey(int beforeDay, int pageStart) {
        long startTime = System.currentTimeMillis();
        logger.info("住院数据按患者清理-分页模式开始,{}", startTime);
        // 分页查询元数据
        List<SfIptPatientdataUpdate> metaList = sfIptPatientdataUpdateMapper.selectByIntervalDay(beforeDay, pageStart, PAGE_LIMIT);
        if (CollectionUtils.isEmpty(metaList)) {
            return;
        }
        // 根据患者信息清理的数据表
        int loopLength = metaList.size() / CLEAR_PATIENT_NUM + 1;
       // CountDownLatch的 count为等待的线程数量,这里execute线程执行是在双层循环内,所以为两者乘积
        CountDownLatch countDownLatch = new CountDownLatch(SfClearConstant.DELETE_IPT_SQL.size() * loopLength);
        ExecutorService dataThreadPool = SfThreadPoolManager.getClearDataThreadPool();
        for (int i = 0; i < loopLength; i++) {
            int start = i * CLEAR_PATIENT_NUM;
            int end = (metaList.size() > (i + 1) * CLEAR_PATIENT_NUM) ? (i + 1) * CLEAR_PATIENT_NUM : metaList.size();

            for (String sql : SfClearConstant.DELETE_IPT_SQL) {
                dataThreadPool.execute(new clearIptDataByKeyThread(sql, metaList.subList(start, end), countDownLatch));
            }
        }

        try {
           // 主线程必须在启动其他线程后立即调用CountDownLatch.await()方法
            countDownLatch.await();
        } catch (InterruptedException e) {
            logger.error("清理线程等待异常", e);
        }
        sfIptPatientdataUpdateMapper.deleteBatch(metaList);
        logger.info("住院数据按患者清理-分页模式结束耗时{}", System.currentTimeMillis() - startTime);
    }

 class clearIptDataByKeyThread implements Runnable {
      public clearIptDataByKeyThread(String sql, List<SfIptPatientdataUpdate> metaList, CountDownLatch countDownLatch) {
            this.sql = sql;
            this.metaList = metaList;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                if (CollectionUtils.isEmpty(metaList)) {
                    return;
                }
                String resultSql = initSQL(metaList.size());
                jdbcTemplate.update(resultSql, new PreparedStatementSetter() {
                    @Override
                    public void setValues(PreparedStatement ps) throws SQLException {
                        for (int i = 0; i < metaList.size(); i++) {
                            SfIptPatientdataUpdate patientInfo = metaList.get(i);
                            ps.setInt(CONDITION_NUM * i + ZONE_ID_INDEX, patientInfo.getZoneId());
                            ps.setString(CONDITION_NUM * i + EVENT_NO_INDEX, patientInfo.getEventNo());
                            ps.setString(CONDITION_NUM * i + PATIENT_ID_INDEX, patientInfo.getPatientId());
                        }
                    }
                });
            } finally {
               //每个线程最后都必须调用countDown()方法,count值就减1
                countDownLatch.countDown();
            }
        }

 

posted on 2018-03-29 20:19 明仔2017 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/lemingyin/p/8672161.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值