javamultithreadingasynchronousrefactoringrunnable

Can i encapsulate the creation of similar threads into a method?


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.


Solution

  • 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()));