<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Hi Mika,</p>
<p>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:</p>
<pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono NL',monospace;font-size:30,0pt;"><span style="color:#cc7832;">import </span>java.util.Map<span style="color:#cc7832;">;
</span><span style="color:#cc7832;">import </span>java.util.concurrent.CancellationException<span style="color:#cc7832;">;
</span><span style="color:#cc7832;">import </span>java.util.concurrent.ConcurrentHashMap<span style="color:#cc7832;">;
</span><span style="color:#cc7832;">
</span><span style="color:#cc7832;">public class </span>CHMPinning {
<span style="color:#cc7832;">public static void </span><span style="color:#ffc66d;">main</span>(String... args) <span style="color:#cc7832;">throws </span>InterruptedException {
Map<Integer<span style="color:#cc7832;">, </span>Integer> map = <span style="color:#cc7832;">new </span>ConcurrentHashMap<>()<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> for </span>(<span style="color:#cc7832;">int </span>i = <span style="color:#6897bb;">0</span><span style="color:#cc7832;">; </span>i < <span style="color:#6897bb;">1_000</span><span style="color:#cc7832;">; </span>i++) {
<span style="color:#cc7832;">int </span>finalI = i<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> </span>Thread.<span style="font-style:italic;">startVirtualThread</span>(() ->
<span style="color:#b389c5;">map</span>.computeIfAbsent(<span style="color:#b389c5;">finalI </span>% <span style="color:#6897bb;">3</span><span style="color:#cc7832;">, </span>key -> {
<span style="color:#cc7832;">try </span>{
Thread.<span style="font-style:italic;">sleep</span>(<span style="color:#6897bb;">2_000</span>)<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> </span>} <span style="color:#cc7832;">catch </span>(InterruptedException e) {
<span style="color:#cc7832;">throw new </span>CancellationException(<span style="color:#6a8759;">"interrupted"</span>)<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> </span>}
<span style="color:#cc7832;">return </span><span style="color:#b389c5;">finalI</span><span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> </span>}))<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> </span>}
<span style="color:#cc7832;">long </span>time = System.<span style="font-style:italic;">nanoTime</span>()<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> try </span>{
Thread.<span style="font-style:italic;">startVirtualThread</span>(() ->
System.<span style="color:#9876aa;font-style:italic;">out</span>.println(
<span style="color:#6a8759;">"Hi, I'm an innocent virtual thread"</span>))
.join()<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> </span>} <span style="color:#cc7832;">finally </span>{
time = System.<span style="font-style:italic;">nanoTime</span>() - time<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> </span>System.<span style="color:#9876aa;font-style:italic;">out</span>.printf(<span style="color:#6a8759;">"time = %dms%n"</span><span style="color:#cc7832;">, </span>(time / <span style="color:#6897bb;">1_000_000</span>))<span style="color:#cc7832;">;
</span><span style="color:#cc7832;"> </span>}
System.<span style="color:#9876aa;font-style:italic;">out</span>.println(<span style="color:#6a8759;">"map = " </span>+ map)<span style="color:#cc7832;">;
</span><span style="color:#cc7832;">
</span><span style="color:#cc7832;"> </span>}
}
</pre>
<p></p>
<p>Output is something like:</p>
<p>Hi, I'm an innocent virtual thread<br>
time = 2002ms<br>
map = {0=0, 1=7, 2=2}<br>
</p>
<p>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.<br>
</p>
<p><br>
</p>
<pre class="moz-signature" cols="72">Regards
Heinz
--
Dr Heinz M. Kabutz (PhD CompSci)
Author of "The Java™ Specialists' Newsletter" - <a class="moz-txt-link-abbreviated" href="http://www.javaspecialists.eu">www.javaspecialists.eu</a>
Java Champion - <a class="moz-txt-link-abbreviated" href="http://www.javachampions.org">www.javachampions.org</a>
JavaOne Rock Star Speaker
Tel: +30 69 75 595 262
Skype: kabutz
</pre>
<div class="moz-cite-prefix">On 2023/01/20 01:22, mikmoila wrote:<br>
</div>
<blockquote type="cite"
cite="mid:CAAosdaBM8_=DX-7QBu-ZuWU3-=i=Q1Dd17n123K7z5Qi3wRXnw@mail.gmail.com">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<div dir="ltr">Hi.
<div><br>
</div>
<div>As often mentioned in this mailing-list a feedback about
preview/incubator features is appreciated, so here's one: </div>
<div><br>
</div>
<div>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.</div>
<div> </div>
<div>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.<br>
</div>
<div><br>
</div>
<div>"-Djdk.tracePinnedThreads=full" reveals that there is a
pinned carrier thread:</div>
<div>
<div>
java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
<== monitors:1<br>
</div>
<div><br>
</div>
</div>
<div>The documentation ( <a
href="https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html#computeIfAbsent(K,java.util.function.Function)"
moz-do-not-send="true" class="moz-txt-link-freetext">https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/util/concurrent/ConcurrentHashMap.html#computeIfAbsent(K,java.util.function.Function)</a>
) says:</div>
<div>
<div><br>
</div>
</div>
<div> "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."</div>
<div><br>
</div>
<div>This is clear but I still found it as a surprise that it
uses synchronized instead of "virtual-thread friendly"
constructs.</div>
<div><br>
</div>
<div>If needed I can post a small demo program.</div>
<div><br>
</div>
<div>JDK used is latest OpenJDK-19 GA, OS is Windows:</div>
<div> openjdk version "19.0.2" 2023-01-17<br>
OpenJDK Runtime Environment (build 19.0.2+7-44)<br>
OpenJDK 64-Bit Server VM (build 19.0.2+7-44, mixed mode,
sharing)<br>
</div>
<div><br>
</div>
<div>Best Regards,</div>
<div> Mika</div>
</div>
</blockquote>
</body>
</html>