在ExecutorCompletionService学习中我们知道,ExecutorCompletionService通过装饰器模式提供了获取已完成的线程结果功能,这大大提高了我们的线程池结果聚合效率。但是这种方式有一种弊端,当我们将已经排好序的结果进行异步提交的时候,获取到的结果是无序的,我们只有通过重新排序或者与之前的排序结果进行对比才能重新得到我们想要的结果,这在开发中无疑提高了代码复杂度,同时也会造成不必要的bug和代码重复。那么我们想一下,能不能有一种方式帮助我们同时使用ExecutorCompletionService的功能,又不会造成结果乱序呢?为此,我对ExecutorCompletionService做了进一步的扩展。大概原理如下:
1 在提交任务时,我们可以获取到线程的future引用,此时通过map建立future和排序的映射关系,作为后续结果排序的依据。
2 在poll出结果后,重新拿到future对象,此时通过排序映射关系获取原始的排序下角标,然后构建一个结果集数组,将结果放入对应的位置。
3 当所有的结果都处理完以后,我们就得到了一个有序的结果数组,但是由于异步执行的不确定性,有些线程可能由于某种原因没有执行完成,导致数组的部分位置是没有元素的。因此,我们需要将数组重新过滤一遍,组装成新的有序列表返回。
下面是源码展示:
package com.tangjianghua.juc.threadpool.extend;
import java.util.*;
import java.util.concurrent.*;
/**
* @auth tangjianghua
* @date 2021/6/15
*/
public class SortedExecutorCompletionService<V> extends ExecutorCompletionService {
/**
* 当前下角标
*/
private int index;
/**
* 位置映射
* future -> index
*/
private Map<Future<V>, Integer> sortedMap = new HashMap<>();
/**
* 位置映射
* v -> index
*/
private Map<V, Integer> vSortedMap = new HashMap<>();
/**
* 是否已经排序
*/
private boolean sorted;
/**
* 结果集
* 如果sorted=true,则该结果集为已排序的结果集
*/
private volatile List<V> result = null;
/**
* 结果集锁
*/
private Object lock = new Object();
public boolean isSorted() {
return sorted;
}
public SortedExecutorCompletionService(Executor executor) {
super(executor);
}
public SortedExecutorCompletionService(Executor executor, BlockingQueue completionQueue) {
super(executor, completionQueue);
}
@Override
public Future submit(Callable task) {
Future future = super.submit(task);
sortedMap.put(future, index++);
return future;
}
/**
* 获取排好序的结果
*
* @param timeOut
* @param timeUnit
* @return 已排序返回 List<V>
* 未排序但已获取结果
*/
public List<V> getAllSortedResult(Long timeOut, TimeUnit timeUnit) {
if (result == null) {
synchronized (lock) {
if (result == null) {
Object[] arrayList = new Object[index];
for (int i = 0; i < index; i++) {
try {
Future<V> future = super.poll(timeOut, timeUnit);
if (future != null) {
V v = future.get();
arrayList[sortedMap.get(future)] = v;
vSortedMap.put(v, sortedMap.get(future));
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
result = new ArrayList<>();
Arrays.stream(arrayList)
.filter(Objects::nonNull)
.forEach(o -> result.add((V) o));
this.sorted = true;
}
}
}
//未排序 但已获取过结果
//将已获取过结果的任务重新排序
if (!sorted) {
synchronized (lock) {
if (!sorted) {
Object[] arrayList = new Object[vSortedMap.size()];
vSortedMap.entrySet().stream()
.forEach(vIntegerEntry -> arrayList[vIntegerEntry.getValue()] = vIntegerEntry.getKey());
result = new ArrayList<>();
Arrays.stream(arrayList)
.filter(Objects::nonNull)
.forEach(o -> result.add((V) o));
}
sorted = true;
}
}
return result;
}
/**
* 获取未排序的结果
*
* @param timeOut
* @param timeUnit
* @return
*/
public List<V> getAllResult(Long timeOut, TimeUnit timeUnit) {
if (result == null) {
synchronized (lock) {
if (result == null) {
result = new ArrayList<>(index);
for (int i = 0; i < index; i++) {
try {
Future<V> future = super.poll(timeOut, timeUnit);
if (future != null) {
V v = future.get();
result.add(v);
vSortedMap.put(v, sortedMap.get(future));
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
return result;
}
}
使用方法
package com.tangjianghua.juc.threadpool.extend;
import com.sun.org.slf4j.internal.Logger;
import com.sun.org.slf4j.internal.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
/**
* @auth tangjianghua
* @date 2021/6/15
*/
public class SortedExecutorCompletionServiceTest{
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
SortedExecutorCompletionService sortedExecutorCompletionService = new SortedExecutorCompletionService<>(threadPoolExecutor);
for (int i = 0; i < 100; i++) {
final int j=i;
sortedExecutorCompletionService.submit(() -> {return j;});
}
// List list = sortedExecutorCompletionService.getAllResult(10L, TimeUnit.SECONDS);
// list.stream().forEach(System.out::println);
List allSortedResult = sortedExecutorCompletionService.getAllSortedResult(10L, TimeUnit.SECONDS);
allSortedResult.stream().forEach(System.out::println);
threadPoolExecutor.shutdown();
}
}