Thursday, 25 December 2014

what is new in java8 ConcurrentHashMap

Java8 has lots of new features that will help in writing code that express its intent more clearly and in concise way.
ConcurrentHashMap has lots of nice features that reduces boiler plate code, most of the new features are parallel, so you don't have to worry about locks/threads etc when using CHM.

Smart Cache
One of the most common usage of CHM is to build cache and it has little concurrency issue that value for key should be computed once and it should provide all the "happens after" guarantee.

Pre JDK8 you have to do some thing like 

private String getJDK7UrlContent(final String key) throws InterruptedException, java.util.concurrent.ExecutionException {
FutureTask<String> urlContentTask = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
return loadUrlContent(key);
}
});
Future<String> content = jdk7UrlContent.putIfAbsent(key,urlContentTask);
if(content==null) {
urlContentTask.run();
content = urlContentTask;
}
return content.get();
}
view raw JDK7putIfAbsent hosted with ❤ by GitHub

JDK8 has special function computeIfAbsent for such type of thing

private String getJDK8UrlContent(String s) {
return jdk8UrlContent.computeIfAbsent(s, (key) -> {
return loadUrlContent(key);
});
}


Difference is obvious for prejdk8 future task is required and some other special handling to make sure it works fine, but JDK8 just call to computeIfAbsent with lamdba expression and your are done.
There is computeIfPresent function also to remap/refresh value in cache.

Merge values 
One of the most common problem with maps is when values of same keys needs to merged, one of the example is you have map of word & count.
Every time word is added to map and if it exists then count needs to be incremented

Example code pre JDK8

public void mergeValue_jdk7(String word, int occurrence) {
Integer count = wordMap.get(word);
if(count==0) {
count=0;
}
wordMap.put(word,count+occurrence);
}
view raw mergeValue_jdk7 hosted with ❤ by GitHub
Example code in JDK8

public void mergeValue_jdk8(String word, int occurrence) {
wordMap.merge(word, occurrence, (key, value) -> wordMap.getOrDefault(key, 0) + value);
}
view raw mergeValue_jdk8 hosted with ❤ by GitHub
All the noise that was there in JDK7 code is removed and code express its intent so nicely

 Aggregate/Reduce values

This is also very common use case where single value is derived from values in map for eg total number of words

sample code for pre JDK8

public int jdk7TotalWords() {
int totalWords=0;
for(Map.Entry<String,Integer> entry : wordMap.entrySet()) {
totalWords+=entry.getValue();
}
return totalWords;
}
view raw jdk7TotalWords hosted with ❤ by GitHub
sample code for JDK8


public int jdk8TotalWords() {
return wordMap.reduceValues(1,(count1,count2) -> count1+count2);
}
view raw jdk8TotalWords hosted with ❤ by GitHub

Code is so so much clean, extra thing to note in jdk8 reduce function is first parameter which is the (estimated) number of elements needed for this operation to be executed in parallel.
So this function supports parallel reducing

Iteration
All the collection of JDK8 has done improvement on iteration method, so application code will never have any loop, it is all controlled by underlying collection and due to which iteration can be done in parallel .

CHM has 2 types of foreach

public void loop_jdk8() {
urlContent.forEach((key, value) -> {
System.out.println(Thread.currentThread().getName() + " Key " + key + " Value " + value);
});
}
public void loop_jdk8_parallel() {
urlContent.forEach(10,(key, value) -> {
System.out.println(Thread.currentThread().getName() + " Key " + key + " Value " + value);
});
}
view raw foreach hosted with ❤ by GitHub
others interesting functions are search

One thing to note is that all the parallel function are executed on common forkjoin pool, there is way by which you can provide your own pool to execute task.

7 comments:

  1. nice code, but unfortunately full of errors...

    ReplyDelete
  2. nice code, but unfortunately full of errors...

    ReplyDelete
    Replies
    1. Thanks for feedback.
      Which part has errors ? It will help me in fixing it.

      Delete
  3. wordMap.reduceValues(1,(count1,count2) -> count1+count2);
    In the above line what is the first argument? if 1 is supposed to be the initial value, shouldnt it be 0 instead?

    ReplyDelete
  4. wordMap.reduceValues(1,(count1,count2) -> count1+count2);
    In the above line what is the first argument? if 1 is supposed to be the initial value, shouldnt it be 0 instead?

    ReplyDelete
  5. Lovely post, thanks for posting.

    ReplyDelete
  6. I find it fascinating how Java8 simplifies complex tasks like merging values and reducing collections.

    ReplyDelete