背景(工作中遇到的坑)
工作项目中,需要根据全部所属地域批量循环查询业务的列表进行展示,为了提升批量查询的速度,我采用的是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,不符合预期。
解决方案和建议
-
使用线程安全的数据结构 :
确保使用线程安全的数据结构来存储结果。例如,可以使用
CopyOnWriteArrayList
或ConcurrentHashMap
代替普通的ArrayList
或HashMap
。inijava List<String> resultList = new CopyOnWriteArrayList<>();
-
合理配置线程池 :
确保线程池的配置与任务量匹配。调整线程池核心线程数、最大线程数和任务队列大小。
-
检测并处理异常 :
捕获并处理所有可能的异常,以防止任务意外终止。
javaexecutorService.execute(() -> { try { // Simulate some work Thread.sleep(100); resultList.add("result"); } catch (Exception e) { e.printStackTrace(); } finally { latch.countDown(); } });
-
适当使用同步机制 :
如果使用非线程安全的数据结构,如
ArrayList
,需要对其进行同步。同样适用于其他共享资源。javaList<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",符合预期。
通过这些改进,可以更好地保证在并发情况下数据不会丢失。