使用多线程批量处理数据
文章目录
为什么要开启多线程处理?道理很简单,如果处理一千条数据需要10ms,那么串行处理1w条就要100ms,假如开启10个线程同时并发处理,只需要约10ms,速度提升将近十倍。当然实际上不会那么简单,需要综合考虑各种因素,但是毫无疑问使用多线程能够大大减少数据处理时长。
基本思路:
- 定义数据组大小,将数据分组。
- 定义任务。
- 确定线程池参数,新建线程池。
- 使用多线程执行任务。
- 合并处理结果。
下面简单来一个代码示例:
private void handleDataWithThreads() {
// 创建模拟数据
List<Person> data = createData(10100);
// 1.数据分组
List<List<Person>> lists = groupData(data, 1000);
// 2.创建任务队列
List<Callable<List<Person>>> taskList = new ArrayList<>();
for (List<Person> list : lists) {
Callable<List<Person>> task = new Callable<List<Person>>() {
@Override
public List<Person> call() throws Exception {
// 此处定义对数据的处理
handleData(list);
return list;
}
};
taskList.add(task);
}
// 3.创建线程池
// 参数:核心线程数;最大线程数;存活时间;存活时间单位;阻塞队列
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 60,
TimeUnit.SECONDS, new LinkedBlockingDeque<>());
try {
// 4.执行任务
List<Future<List<Person>>> futures = executor.invokeAll(taskList);
// 5.合并结果
List<Person> resultList = new ArrayList<>();
for (Future<List<Person>> future : futures) {
resultList.addAll(future.get());
}
System.out.println(resultList);
}catch (Exception e) {
e.printStackTrace();
}finally {
executor.shutdown();
}
}
/**
* 数据处理
* @param data
*/
private void handleData(List<Person> data) {
log.info("当前线程:{},当前处理数据:{} - {}", Thread.currentThread(), data.get(0).getName(), data.get(data.size()-1).getName());
for (Person person : data) {
person.setName("new " + person.getName());
person.setAge(person.getAge() + 10);
}
}
/**
* 生成模拟数据
*/
private List<Person> createData(int total) {
List<Person> people = new ArrayList<>();
for (int i = 0; i < total; i++) {
Person person = new Person("person"+i, 18);
people.add(person);
}
return people;
}
/**
* 数据分组
* @param sourceList
* @param groupNum
* @return
*/
private List<List<Person>> groupData(List<Person> sourceList, int groupNum) {
List<List<Person>> targetList = new ArrayList<>();
int size = sourceList.size();
int remainder = size % groupNum;
int sum = size / groupNum;
for (int i = 0; i<sum; i++) {
List<Person> subList;
subList = sourceList.subList(i * groupNum, (i + 1) * groupNum);
targetList.add(subList);
}
if (remainder > 0) {
List<Person> subList;
subList = sourceList.subList(size - remainder, size);
targetList.add(subList);
}
return targetList;
}
打印结果:
当前线程:Thread[pool-1-thread-10,5,main],当前处理数据:person9000 - person9999
当前线程:Thread[pool-1-thread-7,5,main],当前处理数据:person6000 - person6999
当前线程:Thread[pool-1-thread-1,5,main],当前处理数据:person0 - person999
当前线程:Thread[pool-1-thread-9,5,main],当前处理数据:person8000 - person8999
当前线程:Thread[pool-1-thread-6,5,main],当前处理数据:person5000 - person5999
当前线程:Thread[pool-1-thread-4,5,main],当前处理数据:person3000 - person3999
当前线程:Thread[pool-1-thread-3,5,main],当前处理数据:person2000 - person2999
当前线程:Thread[pool-1-thread-2,5,main],当前处理数据:person1000 - person1999
当前线程:Thread[pool-1-thread-8,5,main],当前处理数据:person7000 - person7999
当前线程:Thread[pool-1-thread-5,5,main],当前处理数据:person4000 - person4999
当前线程:Thread[pool-1-thread-4,5,main],当前处理数据:person10000 - person10099
………
- Java并发方面的知识就不在这里写了(其实我也不是很懂哈哈哈),关于线程池的定义、线程类型的选择等,根据自己的实际情况确定。
- 使用demo中的方式可以确保合并后数据的顺序和原来的一致。如果没有这种需要,可以遍历创建线程对象后直接执行并将结果添加到resultList,但是注意要resultList使用线程安全的类型。
- 影响执行速度的原因有很多,主要原因有数据组大小,线程数,分享本人使用中的一点小心得
-
最好能够使每个线程的执行时间大致相同,避免出现某个线程执行时间过长导致总体时间加长。
-
单个数据处理时间长,则将每组数据量缩减;反之则增加。
-
线程数量其实没有一个统一的公式,总体原则是IO型线程数可以大点,CPU型线程数小点。具体情况还要自己多测试一番。
-
需要好考虑并发的情况,每一次请求都创建一个线程池容易造成阻塞导致服务瘫痪,需要做限制或者创建一个全局线程池,这样即使多个请求进来也只是会影响到需要使用该线程池的方法。