Thread safety while inserting values in hashmap in parallel stream - java

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.

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.

Related

How do I return a Mono<List<Object>> into a HashMap?

I have a hashmap of <Integer, QueryObj> that I need to iterate over and call an external service with. The method signature of my external service is like:
private Mono<List<ReturnedObj>> fetchList(QueryObj query)
and I've been able to verify that it's working and returns a list of what I need. However, I'm unsure of what my next steps should be and what my response type in the parent method should be in order to maintain reactive practices. Basically, I want to transform the Map<Integer, Query> into Map<Integer, Mono<List<ReturnedObj>>. I am wondering if Map<Integer, Mono<List<ReturnedObj>> is even possible? Does it need to be Mono<Map<K<V>>?
Here is the current code snippet - it doesn't throw an error, but rather returns an empty result. I'm thinking the mix of imperative and reactive programming doesn't wait for the results of fetchList() to populate the response.
Map<Integer, QueryObj> queryMap = getQueries(); // setup
return queryMap.entrySet()
.stream()
.collect(Collectors.toMap(
e -> e.getKey(), e -> {
try {
return fetchList(e.getValue());
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}));
}
Would greatly appreciate any help! I am fairly new to this.
Thanks all for the suggestions - I ended up finding a solution that works for me. I ended up making my whole flow reactive (as I should have done from the start) and changed the return type of my method to be Mono<Map<Integer, List>. I used a Flux.fromIterable() with flatMap() on the entrySet and collectMap to get the results together into a map.

Create HashMap from List<String>

I have a few simple JSON files that have syntax
"key1": "value1"
"key2": "value2"
etc.
I need to create a Map from each of them, and then merge them into one. The following code works as is supposed to:
private TranslationBundle assembleBundle(List<String> translations) {
TranslationBundle translationBundle = new TranslationBundle();
Map<String, String> bundledTranslationMap = new HashMap<>();
translations.forEach(translation -> {
bundledTranslationMap.putAll(getSingleTranslationMap(translation).);
});
translationBundle.setTranslationMap(bundledTranslationMap);
return translationBundle;
}
private Map<String, String> getSingleTranslationMap(String translation){
ObjectMapper mapper = new ObjectMapper();
try{
return mapper.readValue(translation, new TypeReference<Map<String, String>>(){});
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
What I want to achieve is to rewrite the code in the assembleBundle method into a more functional one, like
translations.stream().
map(this::getSingleTranslationMap)
.collect(collect to single HashMap);
In the collect() method, as far as I know, I have to somehow access the key and value for each result of getSingleTranslationMap. Since there is no variable name assigned to the result of this method, how should it be done? Maybe I'm trying a wrong approach at all?
You can transform the individual Maps returned by getSingleTranslationMap into a Stream of map entries, and collect them into a single Map:
Map<String, String> bundledTranslationMap =
translations.stream()
.flatMap(translation -> getSingleTranslationMap(translation).entrySet()
.stream())
.collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
Of course, if getSingleTranslationMap returns a Map with a single Entry, it might make more sense for that method to return a Map.Entry instead of a Map, which would simplify the above Stream pipeline.
P.S., if duplicate keys are possible, you should add a merge function to the toMap() call.

How to clear out all the finished Future results of #Async tasks with Spring-Boot?

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:
Iterate the futures list endlessly (0, 1, 2 .. 8, 9, 10, 0, 1, 2...) using endless Iterator.
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
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?
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.

How to conditionally modify a Map in Java 8 stream API?

I am trying to modify a Map's keys based on conditional logic and struggling. I'm new to Java 8 streams API. Let's say I have a map like this:
Map<String, String> map = new HashMap<>();
map.put("PLACEHOLDER", "some_data1");
map.put("Google", "some_data2");
map.put("Facebook", "some_data3");
map.put("Microsoft", "some_data4");
When I would like to do is find the references of PLACEHOLDER and conditionally change that key to something else based on a boolean condition. I feel like it should be something like the below, but this doesn't even compile of course.
boolean condition = foo();
map = map.entrySet().stream().filter(entry -> "PLACEHOLDER".equals(entry.getKey()))
.map(key -> {
if (condition) {
return "Apple";
} else {
return "Netflix";
}
}).collect(Collectors.toMap(e -> e.getKey(), Map.Entry::getValue));
I found this question which kind of makes me think maybe I can't do this with Java 8 stream APIs. Hopefully someone better at this than me knows how to do this. Ideone link if you want to play with it.
You've filtered out all elements that aren't PLACEHOLDER. You need to add that filter logic to your map operation:
final Map<String, String> output = input.entrySet().stream()
.map(e -> {
if (!e.getKey().equals("PLACEHOLDER")) {
return e;
}
if (condition) {
return new AbstractMap.SimpleImmutableEntry<>("Apple", e.getValue());
}
return new AbstractMap.SimpleImmutableEntry<>("Netflix", e.getValue());
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
But as you are guaranteed to only have a single instance of PLACEHOLDER in the Map, you can just do
String placeholderData = input.remove("PLACEHOLDER");
if (placeholderData != null) {
input.put(condition ? "Apple" : "Netflix", placeholderData);
}
If you really want to do it using Streams, you just need to move the conditional logic to the collection phase, like that:
boolean condition = true;
map.entrySet().stream().collect(Collectors.toMap(
entry -> mapKey(entry.getKey(), condition), Map.Entry::getValue
));
where:
private static String mapKey(String key, boolean condition) {
if (!"PLACEHOLDER".equals(key)) {
return key;
}
if (condition) {
return "Apple";
} else {
return "Netflix";
}
}
However, the second part of Boris the Spider's answer using Map.remove and Map.put seems the best way to go.

Store values of multiple keys while collecting into a map stream

Map<String, String> x = ArrayListMultimap.create();
Map<String, Boolean> results1 = Maps.newHashMap();
Map<String, Boolean> results2 = Maps.newHashMap();
I have a multimap which I need to traverse and make some expensive calls. To save time, I want to do a parallel stream. The results I get can have null values which has to be stored in a map. I know how to do this in a non-parallel way, however I'm not able to do this in a parallel stream without getting into concurrency issues. I realize I need to somehow convert this into a map and collect the results, but I don't know how I can return multiple keys and values. I was thinking of having a temporary single map, but that too will have multiple keys.
x.keySet().paralleStream().forEach(req -> {
try {
Response response = getResponseForRequest(req);
if(response.getTitles() != null) {
boolean titleAvail = response.getTitles().stream().allMatch(Avalibilty:Status);
x.get(req).forEach(y -> results1.put(y, titleAvail);
}
if(response.getDetails() != null) {
boolean detailStatus = //perform some stream operation on getDetails
x.get(req).forEach(y -> results2.put(y, detailStatus));
}
} catch(TimeoutException e) {
x.get(req).forEach(y -> {
results1.put(y, null);
results2.put(y, null);
})
} catch(Exception e) {
//log & do nothing
}
});
Eventually what I am trying to do is call getResponseForRequest which returns me a result. And then based on the response, for each key in the multimap, store the results in 2 maps results1, and results2.
I think you can use flatMap().
Declare enum: Availability {TITLE, DETAIL}
Then mapping:
x.entrySet().parallelStream()
.map(entry -> {
// Construct mapping between y and Boolean here
return Map<Y, EnumMap<Availability, Boolean>>;
})
.flatMap(v -> v.entrySet().stream())
.collect(v -> toMap(v.getKey(), v.getValue(), (v1, v2) -> YOUR_DEFINE_MAPPER, ConcurrentHashMap::new));
Hope this help

Categories