【Java】为什么有时候执行countDownLatch+线程池查询列表会丢失数据?

背景(工作中遇到的坑)

工作项目中,需要根据全部所属地域批量循环查询业务的列表进行展示,为了提升批量查询的速度,我采用的是countDownLatch + 线程池的方式。结果,速度是提上来了,但丢了一个地域的查询结果。经排查,是在执行线程池批量查询时,丢失了本应该查询该地域的线程task,所以导致没有查询该所属地域,从而导致了结果的偏差。

分析

使用 CountDownLatch 和线程池进行查询列表时数据丢失通常是由以下因素引起的:

1. 并发问题

当多个线程并发写入共享资源(如列表或map)时,如果没有正确的同步机制,可能会出现数据丢失或覆盖的情况。常见问题包括:

  • 没有正确的同步 :在多线程情况下,访问共享资源时没有进行适当的同步控制(如使用 synchronized 或其他并发工具类)。
  • 竞争条件:多线程同时执行,导致写操作的顺序和结果不可预测。

2. 任务未完全执行

有时候,线程池中的任务还没有完全执行完毕,主线程就已经结束了等待。这可能是由于 CountDownLatch 的计数器没有正确管理导致的。

3. 线程池配置不当

线程池的配置(如核心线程数、最大线程数、队列大小)不匹配任务的要求,导致部分任务无法执行或被丢弃:

  • 线程数限制:线程池中的线程数不足以处理所有任务。
  • 队列溢出:如果任务提交的速度超过线程池处理的速度,而且队列容量已满,新的任务可能被拒绝。

4. 异常处理不当

如果在线程中发生异常,但未被捕获和处理,任务可能会提前终止,从而导致数据的不完整。

代码示例

常见的示例代码如下:

java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        int numberOfThreads = 10;
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
        CountDownLatch latch = new CountDownLatch(numberOfThreads);
        List<String> resultList = new ArrayList<>();

        for (int i = 0; i < numberOfThreads; i++) {
            int threadId = i;
            executorService.execute(() -> {
                try {
                    // 执行体
                    Thread.sleep(100);
                    resultList.add("thread-" + threadId);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executorService.shutdown();
        
        System.out.println("Number of results: " + resultList.size());
    }
}

执行结果:不唯一,有时输出的resultList.size()的值为10,有时会<10,不符合预期。

解决方案和建议

  1. 使用线程安全的数据结构 :

    确保使用线程安全的数据结构来存储结果。例如,可以使用 CopyOnWriteArrayListConcurrentHashMap 代替普通的 ArrayListHashMap

    ini 复制代码
    java
    List<String> resultList = new CopyOnWriteArrayList<>();
  2. 合理配置线程池 :

    确保线程池的配置与任务量匹配。调整线程池核心线程数、最大线程数和任务队列大小。

  3. 检测并处理异常 :

    捕获并处理所有可能的异常,以防止任务意外终止。

    java 复制代码
    executorService.execute(() -> {
        try {
            // Simulate some work
            Thread.sleep(100);
            resultList.add("result");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            latch.countDown();
        }
    });
  4. 适当使用同步机制 :

    如果使用非线程安全的数据结构,如 ArrayList,需要对其进行同步。同样适用于其他共享资源。

    java 复制代码
    List<String> resultList = Collections.synchronizedList(new ArrayList<>());

代码改进

下面是改进后的示例:

java 复制代码
import java.util.List;
import java.util.concurrent.*;

public class CountDownLatchFixedTest {
    public static void main(String[] args) throws InterruptedException {
        int numberOfThreads = 10;
        ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
        CountDownLatch latch = new CountDownLatch(numberOfThreads);
        List<String> resultList = new CopyOnWriteArrayList<>();

        for (int i = 0; i < numberOfThreads; i++) {
            int threadId = i;
            executorService.execute(() -> {
                try {
                    // 执行体
                    Thread.sleep(100);
                    resultList.add("thread-" + threadId);
                } catch (Exception e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executorService.shutdown();
        
        System.out.println("Number of results: " + resultList.size());
    }
}

执行结果:多次验证输出"Number of results: 10",符合预期。

通过这些改进,可以更好地保证在并发情况下数据不会丢失。

相关推荐
xieliyu.6 小时前
Java算法精讲:双指针(三)
java·开发语言·算法
星辰徐哥6 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥6 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约6 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee6 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐6 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs6 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐6 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司6 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
码农阿豪6 小时前
从零到一:Spring Boot快速接入金仓数据库实战
数据库·spring boot·后端