diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtils.java b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtils.java index 01648b615b..552dbb94f7 100644 --- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtils.java +++ b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtils.java @@ -17,9 +17,9 @@ package org.apache.commons.math4.legacy.analysis.polynomials; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.numbers.fraction.BigFraction; import org.apache.commons.numbers.combinatorics.BinomialCoefficient; @@ -82,7 +82,7 @@ public final class PolynomialsUtils { LEGENDRE_COEFFICIENTS.add(BigFraction.ONE); // initialize map for Jacobi polynomials - JACOBI_COEFFICIENTS = new HashMap<>(); + JACOBI_COEFFICIENTS = new ConcurrentHashMap<>(); } /** @@ -228,24 +228,21 @@ public static PolynomialFunction createJacobiPolynomial(final int degree, final // select the appropriate list final JacobiKey key = new JacobiKey(v, w); - if (!JACOBI_COEFFICIENTS.containsKey(key)) { - synchronized(JACOBI_COEFFICIENTS) { - if (!JACOBI_COEFFICIENTS.containsKey(key)) { - // allocate a new list for v, w - final List list = new ArrayList<>(); - JACOBI_COEFFICIENTS.put(key, list); + final List coefficients = JACOBI_COEFFICIENTS.computeIfAbsent(key, k -> { + // allocate a new list for v, w + final List list = new ArrayList<>(); - // Pv,w,0(x) = 1; - list.add(BigFraction.ONE); + // Pv,w,0(x) = 1; + list.add(BigFraction.ONE); - // P1(x) = (v - w) / 2 + (2 + v + w) * X / 2 - list.add(BigFraction.of(v - w, 2)); - list.add(BigFraction.of(2 + v + w, 2)); - } - } - } + // P1(x) = (v - w) / 2 + (2 + v + w) * X / 2 + list.add(BigFraction.of(v - w, 2)); + list.add(BigFraction.of(2 + v + w, 2)); + + return list; + }); - return buildPolynomial(degree, JACOBI_COEFFICIENTS.get(key), + return buildPolynomial(degree, coefficients, new RecurrenceCoefficientsGenerator() { /** {@inheritDoc} */ @Override @@ -368,26 +365,27 @@ private static PolynomialFunction buildPolynomial(final int degree, // Synchronizing on a method parameter is not safe; however, in this // case, the lock object is an immutable field that belongs to this // class. + final double[] a; synchronized (coefficients) { final int maxDegree = (int) JdkMath.floor(JdkMath.sqrt(2.0 * coefficients.size())) - 1; if (degree > maxDegree) { computeUpToDegree(degree, maxDegree, generator, coefficients); } - } - // coefficient for polynomial 0 is l [0] - // coefficients for polynomial 1 are l [1] ... l [2] (degrees 0 ... 1) - // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0 ... 2) - // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0 ... 3) - // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0 ... 4) - // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0 ... 5) - // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0 ... 6) - // ... - final int start = degree * (degree + 1) / 2; - - final double[] a = new double[degree + 1]; - for (int i = 0; i <= degree; ++i) { - a[i] = coefficients.get(start + i).doubleValue(); + // coefficient for polynomial 0 is l [0] + // coefficients for polynomial 1 are l [1] ... l [2] (degrees 0 ... 1) + // coefficients for polynomial 2 are l [3] ... l [5] (degrees 0 ... 2) + // coefficients for polynomial 3 are l [6] ... l [9] (degrees 0 ... 3) + // coefficients for polynomial 4 are l[10] ... l[14] (degrees 0 ... 4) + // coefficients for polynomial 5 are l[15] ... l[20] (degrees 0 ... 5) + // coefficients for polynomial 6 are l[21] ... l[27] (degrees 0 ... 6) + // ... + final int start = degree * (degree + 1) / 2; + + a = new double[degree + 1]; + for (int i = 0; i <= degree; ++i) { + a[i] = coefficients.get(start + i).doubleValue(); + } } // build the polynomial diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtilsTest.java b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtilsTest.java index d6f4014f72..1f6732c6fb 100644 --- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtilsTest.java +++ b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/analysis/polynomials/PolynomialsUtilsTest.java @@ -16,6 +16,13 @@ */ package org.apache.commons.math4.legacy.analysis.polynomials; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + import org.apache.commons.math4.legacy.analysis.UnivariateFunction; import org.apache.commons.math4.legacy.analysis.integration.IterativeLegendreGaussIntegrator; import org.apache.commons.numbers.combinatorics.BinomialCoefficient; @@ -291,6 +298,38 @@ public void testJacobiEvaluationAt1() { } } + @Test + public void testJacobiConcurrentCreation() throws Exception { + final int numThreads = 8; + final ExecutorService exec = Executors.newFixedThreadPool(numThreads); + try { + // Exponents not used elsewhere, so that every (v, w) list is + // created while the threads are racing. + for (int v = 20; v < 40; ++v) { + for (int w = 20; w < 40; ++w) { + final int fv = v; + final int fw = w; + final CyclicBarrier barrier = new CyclicBarrier(numThreads); + final List> results = new ArrayList<>(); + for (int t = 0; t < numThreads; ++t) { + final int degree = 1 + t % 4; + results.add(exec.submit(() -> { + barrier.await(); + return PolynomialsUtils.createJacobiPolynomial(degree, fv, fw).value(1.0); + })); + } + for (int t = 0; t < numThreads; ++t) { + final int degree = 1 + t % 4; + final double binomial = BinomialCoefficient.value(v + degree, degree); + Assert.assertTrue(Precision.equals(binomial, results.get(t).get(), 1)); + } + } + } + } finally { + exec.shutdownNow(); + } + } + @Test public void testJacobiOrthogonality() { for (int v = 0; v < 5; ++v) {