Search code examples
javaconcurrencyjava-8hashmapconcurrenthashmap

Thread safety while inserting values in hashmap in parallel stream


I need to make async calls with a timeout of 10 seconds, and need to perform this for every element from a map. The results of the async calls are stored in another map. Is it safe to use a HashMap in this case or do I need to use ConcurrentMap?

Map<String, String> x = ArrayListMultimap.create();
Map<String, Boolean> value = Maps.newHashMap();

x.keySet().paralleStream().forEach(req -> {
   try {
      Response response = getResponseForRequest(req);
      value.put(req, response.getTitle());
   } catch(TimeoutException e) {
      value.put(req, null);
   }
}

Is this thread safe? I'm not able to figure out. I know the alternative way is to create a concurrent hashmap, and think of some other filler value instead of null as Concurrent maps dont support null values.


Solution

  • You can use .map() instead of .forEach() and return a map created with Collectors.toMap() terminating function instead of modifying external map in parallel. Consider following example:

    Map result = x.keySet()
      .parallelStream()
      .map(req -> {
        try {
          Response response = getResponseForRequest(req);
          return new AbstractMap.SimpleEntry<>(req, response.getTitle());
        } catch (TimeoutException e) {
          return new AbstractMap.SimpleEntry<>(req, null);
        }
      })
      .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
    

    In this example you return a SimpleEntry object that represents a key and value for each element and when all entries are processed you collect them to a single map.

    Simplification

    Holger suggested even more simplified solution by getting rid of AbstractMap.SimpleEntry at all:

    Map result = x.keySet()
      .parallelStream()
      .collect(Collectors.toMap(Function.identity(), req -> {
        try {
          Response response = getResponseForRequest(req);
          return response.getTitle()
        } catch (TimeoutException e) {
          return null
        }
      }));
    

    Pick whatever works better for you.