主要参考
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(); } }