<div dir="ltr"><div><div>Just spitballing,<br></div>there's already a ReservationNode type, adding a ReentrantLock instance variable to it should address the overhead issue (since each ReservationNode is very short-lived, only temporarily in the #table)<br><br></div><div>although the else branch in computeIfAbsent wouldn't be pretty and would require instance of checks</div><div><br></div><div>-T<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Fri, Jan 20, 2023 at 4:51 AM Doug Lea <<a href="mailto:dl@cs.oswego.edu">dl@cs.oswego.edu</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
  
    
  
  <div>
    <p>Updates to ConcurrentHashMap to avoid builtin synchronization are
      on the todo list. As you've noticed, using a ReentrantLock per
      node would add unacceptable overhead, and would be especially
      unwelcome for the huge number of non-loom usages. I have a few
      ideas in mind, but it will surely be a while until arriving at a
      replacement good enough to release.</p>
    <p>-Doug<br>
    </p>
    <div>On 1/20/23 02:40, Dr Heinz M. Kabutz
      wrote:<br>
    </div>
    <blockquote type="cite">
      
      <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:rgb(43,43,43);color:rgb(169,183,198);font-family:"JetBrains Mono NL",monospace"><span style="color:rgb(204,120,50)">import </span>java.util.Map<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">import </span>java.util.concurrent.CancellationException<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">import </span>java.util.concurrent.ConcurrentHashMap<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">
</span><span style="color:rgb(204,120,50)">public class </span>CHMPinning {
    <span style="color:rgb(204,120,50)">public static void </span><span style="color:rgb(255,198,109)">main</span>(String... args) <span style="color:rgb(204,120,50)">throws </span>InterruptedException {
        Map<Integer<span style="color:rgb(204,120,50)">, </span>Integer> map = <span style="color:rgb(204,120,50)">new </span>ConcurrentHashMap<>()<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">        for </span>(<span style="color:rgb(204,120,50)">int </span>i = <span style="color:rgb(104,151,187)">0</span><span style="color:rgb(204,120,50)">; </span>i < <span style="color:rgb(104,151,187)">1_000</span><span style="color:rgb(204,120,50)">; </span>i++) {
            <span style="color:rgb(204,120,50)">int </span>finalI = i<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">            </span>Thread.<span style="font-style:italic">startVirtualThread</span>(() ->
                    <span style="color:rgb(179,137,197)">map</span>.computeIfAbsent(<span style="color:rgb(179,137,197)">finalI </span>% <span style="color:rgb(104,151,187)">3</span><span style="color:rgb(204,120,50)">, </span>key -> {
                        <span style="color:rgb(204,120,50)">try </span>{
                            Thread.<span style="font-style:italic">sleep</span>(<span style="color:rgb(104,151,187)">2_000</span>)<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">                        </span>} <span style="color:rgb(204,120,50)">catch </span>(InterruptedException e) {
                            <span style="color:rgb(204,120,50)">throw new </span>CancellationException(<span style="color:rgb(106,135,89)">"interrupted"</span>)<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">                        </span>}
                        <span style="color:rgb(204,120,50)">return </span><span style="color:rgb(179,137,197)">finalI</span><span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">                    </span>}))<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">        </span>}
        <span style="color:rgb(204,120,50)">long </span>time = System.<span style="font-style:italic">nanoTime</span>()<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">        try </span>{
            Thread.<span style="font-style:italic">startVirtualThread</span>(() ->
                            System.<span style="color:rgb(152,118,170);font-style:italic">out</span>.println(
                                    <span style="color:rgb(106,135,89)">"Hi, I'm an innocent virtual thread"</span>))
                    .join()<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">        </span>} <span style="color:rgb(204,120,50)">finally </span>{
            time = System.<span style="font-style:italic">nanoTime</span>() - time<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">            </span>System.<span style="color:rgb(152,118,170);font-style:italic">out</span>.printf(<span style="color:rgb(106,135,89)">"time = %dms%n"</span><span style="color:rgb(204,120,50)">, </span>(time / <span style="color:rgb(104,151,187)">1_000_000</span>))<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">        </span>}
        System.<span style="color:rgb(152,118,170);font-style:italic">out</span>.println(<span style="color:rgb(106,135,89)">"map = " </span>+ map)<span style="color:rgb(204,120,50)">;
</span><span style="color:rgb(204,120,50)">
</span><span style="color:rgb(204,120,50)">    </span>}
}
</pre>
      <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 cols="72">Regards

Heinz
-- 
Dr Heinz M. Kabutz (PhD CompSci)
Author of "The Java™ Specialists' Newsletter" - <a href="http://www.javaspecialists.eu" target="_blank">www.javaspecialists.eu</a>
Java Champion - <a href="http://www.javachampions.org" target="_blank">www.javachampions.org</a>
JavaOne Rock Star Speaker
Tel: +30 69 75 595 262
Skype: kabutz
</pre>
      <div>On 2023/01/20 01:22, mikmoila wrote:<br>
      </div>
      <blockquote type="cite">
        
        <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)" target="_blank">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>
    </blockquote>
  </div>

</blockquote></div>