ConcurrentHashMap::computeIfAbsent and synchronized

Dr Heinz M. Kabutz heinz at javaspecialists.eu
Fri Jan 20 07:40:24 UTC 2023


Hi Mika,

I was thinking about that yesterday. Since CHM gives a guarantee that 
the compute() functions are called atomically, it might be worth looking 
at the implications of moving (back) to ReentrantLock. However, that 
would also mean that we need to allocate a lot of ReentrantLock 
instances, and those are 40 bytes per lock. Here is a demo that shows 
the issue:

import java.util.Map; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; public class CHMPinning {
     public static void main(String... args)throws InterruptedException {
         Map<Integer, Integer> map =new ConcurrentHashMap<>(); for (int i =0; i <1_000; i++) {
             int finalI = i; Thread.startVirtualThread(() ->
                     map.computeIfAbsent(finalI %3, key -> {
                         try {
                             Thread.sleep(2_000); }catch (InterruptedException e) {
                             throw new CancellationException("interrupted"); }
                         return finalI; })); }
         long time = System.nanoTime(); try {
             Thread.startVirtualThread(() ->
                             System.out.println(
                                     "Hi, I'm an innocent virtual thread"))
                     .join(); }finally {
             time = System.nanoTime() - time; System.out.printf("time = %dms%n", (time /1_000_000)); }
         System.out.println("map = " + map); }
}

Output is something like:

Hi, I'm an innocent virtual thread
time = 2002ms
map = {0=0, 1=7, 2=2}

IMHO, I would not like to see the CHM memory usage increase by 40 x # 
nodes bytes to cater for an edge case of the compute() function taking a 
bit longer.


Regards

Heinz
-- 
Dr Heinz M. Kabutz (PhD CompSci)
Author of "The Java™ Specialists' Newsletter" -www.javaspecialists.eu
Java Champion -www.javachampions.org
JavaOne Rock Star Speaker
Tel: +30 69 75 595 262
Skype: kabutz

On 2023/01/20 01:22, mikmoila wrote:
> Hi.
>
> As often mentioned in this mailing-list a feedback about 
> preview/incubator features is appreciated, so here's one:
>
> I was experimenting with a caching system utilising ConcurrentHashMap 
> as cache store and Structured Concurrency API for refreshing the 
> entries from multiple sources ( StructuredTaskScope.ShutdownOnSuccess 
> ). The idea was to make http-requests for getting the fresh values but 
> the first implementation simply uses UUID::randomUUID for simulating that.
> I noticed that the programs halts In a test case where "N" concurrent 
> calls (where "N" >= number of cpu cores) running on virtual threads 
> end-up calling the ConcurrentHashMap::computeIfAbsent for the same 
> (non-existing) key.
>
> "-Djdk.tracePinnedThreads=full" reveals that there is a pinned carrier 
> thread:
> java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) 
> <== monitors:1
>
> The documentation ( 
> https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html#computeIfAbsent(K,java.util.function.Function) 
> ) says:
>
>   "Some attempted update operations on this map by other threads may 
> be blocked while computation is in progress, so the computation should 
> be short and simple."
>
> This is clear but I still found it as a surprise that it uses 
> synchronized instead of "virtual-thread friendly" constructs.
>
> If needed I can post a small demo program.
>
> JDK used is latest OpenJDK-19 GA, OS is Windows:
>   openjdk version "19.0.2" 2023-01-17
>   OpenJDK Runtime Environment (build 19.0.2+7-44)
>   OpenJDK 64-Bit Server VM (build 19.0.2+7-44, mixed mode, sharing)
>
> Best Regards,
>   Mika
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20230120/13d3f8c2/attachment.htm>


More information about the loom-dev mailing list