Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8294891

ConcurrentHashMap recursion prevention is too strict

XMLWordPrintable

    • b01
    • 11
    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      $ java --version
      openjdk 19 2022-09-20
      OpenJDK Runtime Environment (build 19+36-2238)
      OpenJDK 64-Bit Server VM (build 19+36-2238, mixed mode, sharing)

      A DESCRIPTION OF THE PROBLEM :
      ConcurrentHashMap has a recursion prevention. This recursion prevention surprises me (why not let the Stack overflow in the first place). But furthermore, this recursion prevention is too strict. It prevents ConcurrentHashMap from being used for memoizatoin.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Create a recursive function (like Fibonacci) and try to use computeIfAbsent() of ConcurrentHashMap for memoization.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Using ConcurrentHashMap for memoization works just fine.
      ACTUAL -
      ConcurrentHashMap has a mechanism to prevent recursive updates.

      Exception in thread "main" java.lang.IllegalStateException: Recursive update
              at java.base/java.util.concurrent.ConcurrentHashMap.transfer(ConcurrentHashMap.java:2552)
              at java.base/java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2354)
              at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1776)
              at Fibonacci.fibonacci(Fibonacci.java:14)
              at Fibonacci.lambda$fibonacci$0(Fibonacci.java:14)
              at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
              at Fibonacci.fibonacci(Fibonacci.java:14)
              at Fibonacci.main(Fibonacci.java:17)


      ---------- BEGIN SOURCE ----------
      import java.util.Map;
      import java.util.concurrent.ConcurrentHashMap;

      public class Fibonacci {
          private static Map<Long, Long> table = new ConcurrentHashMap<>();

          public static Long fibonacci(long n) {
              return table.computeIfAbsent(n, m -> m < 2 ? m : fibonacci(m - 1) + fibonacci(m - 2));
          }

          public static void main(final String... args) {
              System.out.println(fibonacci(Long.parseLong(args[0])));
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The workaround is to "manually" memoize instead of using computeIfAbsent(). Like this:
      i
      mport java.util.Map;
      import java.util.concurrent.ConcurrentHashMap;

      public class Fibonacci {
          private static Map<Long, Long> table = new ConcurrentHashMap<>();
          public static Long fibonacci(long n) {
              synchronized (table) {
                  if (!table.containsKey(n)) {
                      long fibonacci = n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
                      table.put(n, fibonacci);
                  }
                  return table.get(n);
              }
          }
          public static void main(final String... args) {
              System.out.println(fibonacci(Long.parseLong(args[0])));
          }
      }

      The workaround is potentially slow for locking the memoization cache for longer durations than actually required. Given the performance benefits of memoization, that may still be worth it. The workaround also demonstrates that the recursion is not an issue (it terminates).

      Other workarounds: Don't use recursion, or look for faster formulas if available.

      FREQUENCY : often


            smarks Stuart Marks
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: