I have a list of objects (from model class Name) that I want to process using an async method. The async method will take each Name object in the list and process it (check if it's not taken), I want to have a list of Names after they all get processed by the async method to get returned to the isNameAvailable(LinkedHashSet<Name> nameList)
method
My executor config
@Configuration
@ManagedResource
public class ThreadConfig {
@Bean(name = "nameAvailabilityExecutor")
public Executor nameAvailabilityExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(100);
executor.setMaxPoolSize(1200);
executor.setQueueCapacity(10000);
executor.setThreadNamePrefix("nameAvailabilityExecutor-");
executor.initialize();
return executor;
}
}
Service class which will call the Async method in another class
public LinkedHashSet<Name> isNameAvailable(LinkedHashSet<Name> nameList) {
LinkedHashSet<Name> nameCheckedList = new LinkedHashSet<>();
for (Name nameObj : nameList) {
Name nameCheckedObj = domainAvailabilityServiceThread.isNameAvailable(nameObj);
nameCheckedList.add(nameCheckedObj);
}
return nameCheckedList;
}
The async method which will do the processing
@Async("nameAvailabilityExecutor")
public Name isNameAvailable(Name nameObj) {
String name = nameObj.getName();
if (getByNameCheck(name)) {
nameObj.setAvailable(true);
} else {
nameObj.setAvailable(false);
}
return nameObj;
}
From my understanding CompletableFuture is what I need to use here? What is the correct way using CompletableFuture in this scenario?
CodePudding user response:
@Async
can be used for fire-and-forget scenarios, such as sending an email, kicking off a database job, some long-running background task. Caller immediately gets the response, while a background job continues processing.
So it's not a good choice for your requirements since you want to return come result after completion.
Well, CompletableFuture
API would be a good choice for you. You can combine them, merge results and etc, putting the callbacks to them.
CodePudding user response:
You can simply make isNameAvailable(Name)
return a CompletableFuture
:
@Async("nameAvailabilityExecutor")
public CompletableFuture<Name> isNameAvailable(Name nameObj) {
String name = nameObj.getName();
if (getByNameCheck(name)) {
nameObj.setAvailable(true);
} else {
nameObj.setAvailable(false);
}
return CompletableFuture.completedFuture(nameObj);
}
Spring @Async
will deal with the asynchronous execution as you intended.
You will also need to change the return type of isNameAvailable(LinkedHashSet)
to a simple List
or something similar, since it does not really make sense to store CompletableFuture
s in a Set
:
public List<CompletableFuture<Name>> isNameAvailable(LinkedHashSet<Name> nameList) {
List<CompletableFuture<Name>> nameCheckedList = new ArrayList<>();
for (Name nameObj : nameList) {
CompletableFuture<Name> nameCheckedObj = domainAvailabilityServiceThread.isNameAvailable(nameObj);
nameCheckedList.add(nameCheckedObj);
}
return nameCheckedList;
}
Note that it is probably not a good idea to asynchronously modify the state of an object like you are doing here with Name
, as it makes it more difficult to guarantee what state will be visible to the calling thread. It might be preferable to work with CompletableFuture<Boolean>
:
@Async("nameAvailabilityExecutor")
public CompletableFuture<Boolean> isNameAvailable(Name nameObj) {
String name = nameObj.getName();
return CompletableFuture.completedFuture(getByNameCheck(name));
}
and return a Map<Name, CompletableFuture<Boolean>>
:
public Map<Name, CompletableFuture<Boolean>> isNameAvailable(LinkedHashSet<Name> nameList) {
Map<Name, CompletableFuture<Boolean>> nameCheckedList = new HashMap<>();
for (Name nameObj : nameList) {
CompletableFuture<Boolean>> nameCheckedObj = domainAvailabilityServiceThread.isNameAvailable(nameObj);
nameCheckedList.put(nameObj, nameCheckedObj);
}
return nameCheckedList;
}
and let the calling thread do whatever is needed with that check.