问题
Java 8 environment.
run tasks using CompletableFuture.allOf() concurrently and then get each result from each thread and then combine all results into one combinedResult and return it.
In the below code, to get the result ( = List<Student>
) it doesn't have to be the code between I. and II.
They say I need to use join() but didn't work
I also got some allOf() from
Java 8 CompletableFuture.allOf(...) with Collection or List
and other links, but nothing works for me.
I think I miss some very easy part. Does someone know how to make it work?
public class Test1 {
public static void main(String[] args) {
Test1 t = new Test1();
Map<Major, List<Student>> allMajorStudentListMap = new HashMap<>();
// fill out some data toallMajorStudentListMap
t.getData(allMajorStudentListMap);
}
List<Student> getData(Map<Major, List<Student>> allMajorStudentListMap) {
List<CompletableFuture<List<Student>>> completableFutures = new ArrayList<>();
// suppose the size of completableFutures is 10
for(Map.Entry<Major, List<Student>> entry: allMajorStudentListMap.entrySet()) {
CompletableFuture<List<Student>> future = CompletableFuture.supplyAsync(() -> getDetailedStudents(entry));
completableFutures.add(future);
}
// want to run 10 jobs concurrently --> get the 10 result and then combine these 10 results into one
// finally want to sent the combined 10 results at one in this method
// I. ======================= I got this code from somewhere ==========================
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]))
.exceptionally(ex -> null)
.join();
Map<Boolean, List<CompletableFuture<List<Student>>>> result =
completableFutures.stream()
.collect(Collectors.partitioningBy(CompletableFuture::isCompletedExceptionally));
result.forEach((k, clist) -> {
System.out.printf("k = " + k);
for(CompletableFuture<List<Student>> student: clist) {
// 3) don't know how to get only List<Student> and then print here
// tried this and that but didn't work
// student.get() has compile error
}
});
// II. =============================================================================================
// want to return combined List<Student>
return ???;
}
List<Student> getDetailedStudents(Map.Entry<Major, List<Student>> entry)
{
List<Student> studentList = new ArrayList<>();
Major major = entry.getKey();
String majorCode = major.getMajorCode();
String majorName = major.getMajorName();
List<Student> studentListList = entry.getValue();
studentList.addAll(getDataFromRemote(majorCode, majorName, studentList)));
return studentList;
}
List<Student> getDataFromRemote(String majorCode, String majorName, List<studentList> studentList) {
// do something and then return list of Student
return detailedStudentList;
}
}
回答1:
Here I have created a slightly altered (directly used List<Student>
instead of Map<K,V>
) version of working sample. You can compare your solution with this one.
Totally five times the students list is queried and each gets executed concurrently which returns a completable list of students with ONE student object in it after an artificial delay of 3 seconds. So in theory if each runs concurrently, after 3 seconds of delay all of the 5 student object should get displayed.
if you notice the time gap between start and end of the main method, it will be around 3 seconds.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.*;
import java.util.stream.Collectors;
public class CompletableFutureTest {
private static int counter = 0;
public static void main(String[] args) {
System.out.println("Program started at " + new Date());
List<Student> allStudents = new ArrayList<>();
new CompletableFutureTest().getData(allStudents);
for(Student st : allStudents){
System.out.println(st.getName());
}
System.out.println("Program ended at " + new Date());
}
private void getData(List<Student> resultToFillIn){
List<CompletableFuture<List<Student>>> completableFutures = new ArrayList<>();
//for simulation purpose just running regular for loop 5 times instead of yours
final Integer integer = new Integer(0);
for(int i=0; i < 5; i++){
completableFutures.add(CompletableFuture.supplyAsync(() -> getStudentsBatch()));
}
CompletableFuture<List<Student>>[] cfArray = new CompletableFuture[completableFutures.size()];
cfArray = completableFutures.toArray(cfArray);
CompletableFuture.allOf(cfArray)
.exceptionally(ex ->
{
ex.printStackTrace();
return null;
}).join();
List<CompletableFuture<List<Student>>> completedFutures = completableFutures.stream().filter(cf -> !cf.isCompletedExceptionally()).collect(Collectors.toList());
for(CompletableFuture<List<Student>> cf : completedFutures){
resultToFillIn.addAll(cf.join());
}
}
private List<Student> getStudentsBatch() {
//adding some delay
try {
Thread.sleep( 3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
List<Student> students = new ArrayList<>();
Student student = new Student();
student.setName("Student " + ++counter);
students.add(student);
return students;
}
public static class Student{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class Major{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
Since this works as explained above, CompletableFuture.allOf(...)
is working.
But, try avoid using join()
whenever you can, as it will stall the currently executing thread. If you opt for truly asynchronous programming, then instead of join()
you can use thenAccept(x -> {})
, thenApply(x -> {})
callback methods.
Hope this helps you.
来源:https://stackoverflow.com/questions/59183298/how-to-get-result-from-completablefuturelistcustomobject-in-java-8