1、前言
当我们需要处理批量任务的时候,经常会使用线程池来提高处理速度,但线程池的使用方式有多种,今天主要来介绍一下ExecutorService、CompletionService和CompletableFuture的区别。
2、结论
这里先说结论,后面有代码论证
● ExecutorService:适用于需要管理和调度一组线程任务的场景,可以通过 Future 获取任务结果,它是根据任务的提交顺序来获取结果的。
● CompletionService:适用于需要按任务完成顺序处理结果的场景,基于 ExecutorService 实现。
● CompletableFuture:适用于复杂的异步编排和组合任务,提供更丰富的功能和流式 API,适合现代异步编程需求。 CompletableFuture.supplyAsync,然后再同步调用thenApply。
3代码论证
3.1、ExecutorService
ExecutorService是根据任务提交的顺序来获取任务处理结果的,也就是先提交的任务先会被获取结果,哪怕后提交的任务先被处理完成了。
package com.myf.Test.Test.current;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolTest {
public static AtomicInteger ThreadIndex = new AtomicInteger(0);
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
1000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), task -> {
Thread thread = new Thread(task);
thread.setName("threadPoolExecutor-" + ThreadIndex);
ThreadIndex.incrementAndGet();
return thread;
});
public static void main(String[] args) {
List<Future<String>> futureList = new ArrayList<>();
for (int i = 10; i > 0; i--) {
String a = i + "";
futureList.add(threadPoolExecutor.submit(() -> add(a)));
}
System.out.println("开始获取结果:" + LocalDateTime.now());
for (Future<String> future : futureList) {
try {
System.out.println(future.get() + ";" + LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static String add(String str) {
try {
Thread.sleep(Integer.parseInt(str) * 500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return str + str + ";" + Thread.currentThread().getName();
}
}
如上我们创建了threadPoolExecutor线程池,核心线程池和最大线程数都是10,也就是全部都是核心线程,在main方法里创建了10个任务来使用线程池,add方法里做了线程休眠操作,第一个任务会休眠5s,第二个任务会休眠4.5s,以此类推,可知,后提交的任务休眠时间越来越短,最先被处理完,同时将处理任务的线程name打印了出来,我们来看下运行结果。
开始获取结果:2024-09-17T16:14:24.969
1010;threadPoolExecutor-0;2024-09-17T16:14:29.938
99;threadPoolExecutor-1;2024-09-17T16:14:29.938
88;threadPoolExecutor-2;2024-09-17T16:14:29.938
77;threadPoolExecutor-3;2024-09-17T16:14:29.938
66;threadPoolExecutor-4;2024-09-17T16:14:29.939
55;threadPoolExecutor-5;2024-09-17T16:14:29.939
44;threadPoolExecutor-6;2024-09-17T16:14:29.939
33;threadPoolExecutor-7;2024-09-17T16:14:29.939
22;threadPoolExecutor-8;2024-09-17T16:14:29.939
11;threadPoolExecutor-9;2024-09-17T16:14:29.939
可以看到任务全部提交完到获取到第一个任务的处理结果耗时5s左右,就是因为提交的第一个任务休眠了5s导致的。
3.2、CompletionService
CompletionService是根据任务完成的顺序来获取执行结果。
package com.myf.Test.Test.current;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletionServiceTest {
public static AtomicInteger ThreadIndex = new AtomicInteger(0);
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
1000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), task -> {
Thread thread = new Thread(task);
thread.setName("threadPoolExecutor-" + ThreadIndex);
ThreadIndex.incrementAndGet();
return thread;
});
public static void main(String[] args) {
CompletionService executorCompletionService= new ExecutorCompletionService<>(threadPoolExecutor);
List<Future<String>> futureList = new ArrayList<>();
for (int i = 10; i > 0; i--) {
String a = i + "";
futureList.add(executorCompletionService.submit(() -> add(a)));
}
System.out.println("开始获取结果:" + LocalDateTime.now());
for (Future<String> future : futureList) {
try {
System.out.println(executorCompletionService.take().get() + ";" + LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static String add(String str) {
try {
Thread.sleep(Integer.parseInt(str) * 500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return str + str + ";" + Thread.currentThread().getName();
}
}
通过executorCompletionService.take().get()可以阻塞式的获取优先执行完的结果
开始获取结果:2024-09-17T16:21:45.204
11;threadPoolExecutor-9;2024-09-17T16:21:45.657
22;threadPoolExecutor-8;2024-09-17T16:21:46.157
33;threadPoolExecutor-7;2024-09-17T16:21:46.656
44;threadPoolExecutor-6;2024-09-17T16:21:47.158
55;threadPoolExecutor-5;2024-09-17T16:21:47.657
66;threadPoolExecutor-4;2024-09-17T16:21:48.157
77;threadPoolExecutor-3;2024-09-17T16:21:48.658
88;threadPoolExecutor-2;2024-09-17T16:21:49.157
99;threadPoolExecutor-1;2024-09-17T16:21:49.657
1010;threadPoolExecutor-0;2024-09-17T16:21:50.157
和前面一个相比,提交任务的顺序没有发生变化,但是获取任务执行结果的顺序变了,是因为后提交的任务休眠时间最短,所以会最先被执行完,而且可以看到每个任务执行完的时间都相差500ms左右。符合预期。
3.3、CompletableFuture
如果我们有这样的一个场景,需要对批量任务执行完的结果做一些逻辑处理,那么我们就可以使用CompletableFuture了。
package com.myf.Test.Test.current;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
public static AtomicInteger ThreadIndex = new AtomicInteger(0);
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
1000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), task -> {
Thread thread = new Thread(task);
thread.setName("threadPoolExecutor-" + ThreadIndex);
ThreadIndex.incrementAndGet();
return thread;
});
public static void main(String[] args) {
List<CompletableFuture<String>> futureList = new ArrayList<>();
for (int i = 10; i > 0; i--) {
String a = i + "";
futureList.add(CompletableFuture.supplyAsync(() -> add(a), threadPoolExecutor)
.thenApplyAsync(CompletableFutureTest::add2, threadPoolExecutor));
}
System.out.println("开始获取结果:" + LocalDateTime.now());
for (CompletableFuture<String> future : futureList) {
try {
System.out.println(future.get() + ";" + LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static String add(String str) {
try {
Thread.sleep(Integer.parseInt(str) * 500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return str + str + ";" + Thread.currentThread().getName();
}
public static String add2(String str) {
return str + ";" + Thread.currentThread().getName();
}
}
在这里我们增加了一个add2方法,在任务执行完add方法后,会拿add的出参作为add2方法的入参来运行。
开始获取结果:2024-09-17T16:38:31.378
1010;threadPoolExecutor-0;threadPoolExecutor-0;2024-09-17T16:38:36.331
99;threadPoolExecutor-1;threadPoolExecutor-1;2024-09-17T16:38:36.331
88;threadPoolExecutor-2;threadPoolExecutor-2;2024-09-17T16:38:36.331
77;threadPoolExecutor-3;threadPoolExecutor-3;2024-09-17T16:38:36.332
66;threadPoolExecutor-4;threadPoolExecutor-4;2024-09-17T16:38:36.332
55;threadPoolExecutor-5;threadPoolExecutor-5;2024-09-17T16:38:36.332
44;threadPoolExecutor-6;threadPoolExecutor-6;2024-09-17T16:38:36.332
33;threadPoolExecutor-7;threadPoolExecutor-7;2024-09-17T16:38:36.332
22;threadPoolExecutor-8;threadPoolExecutor-8;2024-09-17T16:38:36.332
11;threadPoolExecutor-9;threadPoolExecutor-9;2024-09-17T16:38:36.332
我们可以看到,在add方法执行完之后紧接着用add的出参作为入参来来执行add2方法了。但这里获取任务执行结果是根据任务提交的顺序来排序的,这一点可能会影响效率。
当然我们也可以改成按照任务完成的顺序来获取结果
package com.myf.Test.Test.current;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CompletableFutureTest {
public static AtomicInteger ThreadIndex = new AtomicInteger(0);
public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
1000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), task -> {
Thread thread = new Thread(task);
thread.setName("threadPoolExecutor-" + ThreadIndex);
ThreadIndex.incrementAndGet();
return thread;
});
public static void main(String[] args) {
List<CompletableFuture<String>> futureList = new ArrayList<>();
for (int i = 10; i > 0; i--) {
String a = i + "";
futureList.add(CompletableFuture.supplyAsync(() -> add(a), threadPoolExecutor)
.thenApplyAsync(CompletableFutureTest::add2, threadPoolExecutor));
}
System.out.println("开始获取结果:" + LocalDateTime.now());
List<CompletableFuture<Void>> orderedFutures = new ArrayList<>();
for (CompletableFuture<String> future : futureList) {
CompletableFuture<Void> orderedFuture = future.thenAccept(result -> {
System.out.println(result + ";" + LocalDateTime.now());
});
orderedFutures.add(orderedFuture);
}
// 等待所有任务完成
CompletableFuture.allOf(orderedFutures.toArray(new CompletableFuture[0]));
}
public static String add(String str) {
try {
Thread.sleep(Integer.parseInt(str) * 500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return str + str + ";" + Thread.currentThread().getName();
}
public static String add2(String str) {
return str + ";" + Thread.currentThread().getName();
}
}
借助allof来执行,而不是直接通过feature.get来获取执行结果。
开始获取结果:2024-09-17T17:32:18.482
11;threadPoolExecutor-9;threadPoolExecutor-9;2024-09-17T17:32:18.941
22;threadPoolExecutor-8;threadPoolExecutor-8;2024-09-17T17:32:19.442
33;threadPoolExecutor-7;threadPoolExecutor-7;2024-09-17T17:32:19.941
44;threadPoolExecutor-6;threadPoolExecutor-6;2024-09-17T17:32:20.445
55;threadPoolExecutor-5;threadPoolExecutor-5;2024-09-17T17:32:20.944
66;threadPoolExecutor-4;threadPoolExecutor-4;2024-09-17T17:32:21.444
77;threadPoolExecutor-3;threadPoolExecutor-3;2024-09-17T17:32:21.943
88;threadPoolExecutor-2;threadPoolExecutor-2;2024-09-17T17:32:22.445
99;threadPoolExecutor-1;threadPoolExecutor-1;2024-09-17T17:32:22.944
1010;threadPoolExecutor-0;threadPoolExecutor-0;2024-09-17T17:32:23.446