I'm writing a search function that is doing multiple API calls that i would like to execute asynchronous and collect their results. All my threads with their runnables look similar which made we wonder if i could encapsulate the those threads into a method since only 2 lines change per thread.
It looks similar to this:
List<BookResponse> allMatches = Collections.synchronizedList(new ArrayList<>());
List<Thread> threads = new ArrayList<>();
Thread searchBookName = new Thread(() -> {
try {
String someParam = someMethod();
List<BookResponse> matches = fetchMethod(someParam);
synchronized (allMatches) {
allMatches.addAll(matches);
}
} catch (Exception e) {
throw new CustomException(e);
}
});
threads.add(searchBookName);
searchBookName.start();
Thread searchBookContent = new Thread(() -> {
try {
int intParam = anyMethod();
List<BookResponse> matches = differentFetchMethod(intParam);
synchronized (allMatches) {
allMatches.addAll(matches);
}
} catch (Exception e) {
throw new CustomException(e);
}
});
threads.add(searchBookContent);
searchBookContent.start();
/*
*
*
* More Threads...
*
*
*/
for (Thread search : searches) {
search.join();
}
return new ResponseEntity<List<BookResponse>>(allMatches, HttpStatus.OK);
These thread blocks take up a lot of space in the code and are very repetetive, they are made in this pattern with only the 2 commented lines changing:
Thread searchSomething = new Thread(() -> {
try {
//Always 1 method call for a param, params are different types
//Always 1 api call giving back results
synchronized (allMatches) {
allMatches.addAll(matches);
}
} catch (Exception e) {
throw new CustomException(e);
}
});
threads.add(searchSomething);
searchSomething.start();
I tried to come up with an interface to solve this, but i end up having to implements those 2 lines of code anyways somehow, so i that didn't make the code any cleaner.
You aren't making or sub-classing Threads you should be making Runnables (also look at Future, Callable, Exectutor and thread pools).
abstract class AbstractBookSearcher<ParamType> implements Runnable {
private final ParamType searchParam;
AbstractBookSearcher(ParamType searchParam) {
this.searchParam = searchParam;
}
public void run() throws Exception {
try {
List<BookResponse> matches = search(searchParam);
synchronized (allMatches) {
allMatches.addAll(matches);
}
} catch (Exception e) {
throw new CustomException(e);
}
}
protected abstract List<BookResponse> search(ParamType searchParam);
}
Then sub-class that:
class BookNameSearcher extends AbstractBookSearcher<String> {
protected abstract List<BookResponse> search(String searchParam) {
return fetchMethod(searchParam);
}
}
or:
class BookContentSearcher extends AbstractBookSearcher<Integer> {
protected abstract List<BookResponse> search(Integer searchParam) {
return differentFetchMethod(searchParam);
}
}
Then you can start the threads with the custom and type safe runnables, with the search parameters that you want:
new Thread(new BookNameSearcher(someMethod()));
new Thread(new BookContentSearcher(anyMethod()));