3

I have trouble understanding the usage of @Async annotation and methods since I am quite inexperienced in the multithreading field.

I have some Map<Integer, String> map and I want to update this map using the asynchronous method calls. This method makes HTTP GET request on a page with int id which returns String string as result - it is updated to the map and I need to get the updated result value:

@Async
public Future<Entry<Integer, String>> updateEntry(Integer key, String value) {
    Entry<Integer, String> entry = // Method call updates String value (let's say HTTP GET request takes few seconds)
    return new AsyncResult<Entry<Integer, String>>(entry);
}

Since this method won't work if called using this, there is another class executing those methods:

private final Map<Integer, String> map = new ConcurrentHashMap<>();

private void update() {

    // KICK THE ASYNC METHODS UP
    List<Future<Entry<Integer, String>>> futures = new ArrayList<>();
    map.forEach((key, value) -> {
        futures.add(asyncObject.updateEntry(value));
    }); 

    // FETCH THE RESULTS
    Iterator<Future<Entry<Integer, String>>> iterator = futures.iterator();
    while (iterator.hasNext()) {
        Future<Entry<Integer, String>> future = iterator.next();
        if (future.isDone()) {
            try {
                Entry<Integer, String> entry = future.get();
                this.map.put(entry.getKey(), entry.getValue());
                iterator.remove();
            } catch (InterruptedException | ExecutionException e) {
                // SH*T HAPPENS, LOG IT
            }
        }
        if (!iterator.hasNext()) {
            iterator = futures.iterator();
        }
    }
}

I have stored all the future results in futures and the result sooner or later will appear. I think a good way is to use endless while-loop to check if Future::isDone, the iteration above is briefly described below:

  1. Iterate the futures list endlessly (0, 1, 2 .. 8, 9, 10, 0, 1, 2...) using endless Iterator.
  2. If Future<Entry<Integer, String>> is done:
    • Update the map using Map::put which replaces the entry
    • Remove Future<Entry<Integer, String>> from the List
  3. Stop the endless iteration if the list futures is empty.

This solution surprisingly (my first multithreading experiment) works.

Is this solution thread safe and of a correct approach? Is the usage of ConcurrentHashMap sufficient? I have no idea how to test unless printing the actions to the console or log. What might happen if the map would be updated by another @Async call at the moment of update method call - would be making this method synchronized sufficent?

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183

1 Answers1

1

As for me approach looks good. Nothing should happen if map updated during update method run since ConcurrentHashMap is used. If you want prevent simultaneous update run you can make method synchronized or use some lock. You can take a look on CompletableFuture and allOf it will manage futures for you and emit new one when all futures done.

Georgy Gobozov
  • 13,633
  • 8
  • 72
  • 78
  • Thank you for the answer. Are you sure that the ConcurrentHashMap is sufficent for this use case? Anyway, have my +1 and let me be honored to move you among 10k guys ;)) – Nikolas Charalambidis Aug 20 '18 at 11:34
  • Just a note, I don’t want to wait for all the Futures becaus eone might finish immediatelly while another in a minute. I want a continuous update. – Nikolas Charalambidis Aug 20 '18 at 11:41