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