双重检查锁定可增长的二项式系数数组

时间:2010-08-29 19:11:45

标签: java locking java-memory-model double-checked-locking binomial-coefficients

我正在尝试使用双重检查锁定来维护二项式系数数组,但我最近读到双重检查锁定不起作用。效率非常重要,因此使用volatile不是一种选择,除非它只在条件语句中。我看不到一种方法来使用带有单例对象的静态类(这是框架的一部分,我不知道人们需要使用该函数的数字类型,所以我无法猜出最大值是多少选择的值将是或将是否将使用该功能)。我唯一能想到的是使一切都不是静态的,并坚持要求使用此方法的每个线程使用自己的数组实例化一个Choose对象。似乎这不应该是必要的。

public static final class Util{
/**
 * Static array of nCr values
 */
public static long[][] nCr_arr;

/**
 * Calculate binomial coefficient (n k)
 * 
 * @param n
 *            n
 * @param k
 *            k
 * @return n choose k
 */
public static long nCr(int n, int k) {
    if (k < 0)
        throw new ArithmeticException("Cannot choose a negative number");
    if (n < 0) {
        if (k % 2 == 0)
            return nCr(-n + k - 1, k);
        else
            return -nCr(-n + k - 1, k);
    }
    if (k > n)
        return 0;
    if (k > n / 2)
        k = n - k;
    if (nCr_arr == null) {
        synchronized (Util.class) {
            if (nCr_arr == null)
                nCr_arr = new long[n + 1][];
        }
    }
    if (nCr_arr.length <= n) {
        synchronized (Util.class) {
            if (nCr_arr.length <= n) {
                long[][] newNCR = new long[n + 1][];
                System.arraycopy(nCr_arr, 0, newNCR, 0, nCr_arr.length);
                nCr_arr = newNCR;
            }
        }
    }
    if (nCr_arr[n] == null) {
        synchronized (Util.class) {
            if (nCr_arr[n] == null)
                nCr_arr[n] = new long[k + 1];
        }
    }
    if (nCr_arr[n].length <= k) {
        synchronized (Util.class) {
            if (nCr_arr[n].length <= k) {
                long[] newNCR = new long[k + 1];
                System.arraycopy(nCr_arr[n], 0, newNCR, 0,
                        nCr_arr[n].length);
                nCr_arr[n] = newNCR;
            }
        }
    }
    if (nCr_arr[n][k] == 0) {
        if (k == 0)
            nCr_arr[n][k] = 1;
        else
            nCr_arr[n][k] = nCr(n, k - 1) * (n - (k - 1)) / k;
    }
    return nCr_arr[n][k];
}
}

7 个答案:

答案 0 :(得分:0)

嗯,您可以通过更改以下代码来避免双重检查锁定:

if (nCr_arr == null) {
    synchronized (Util.class) {
        if (nCr_arr == null)
            nCr_arr = new long[n + 1][];
    }
}

到此:

synchronized (Util.class) {
    if (nCr_arr == null)
        nCr_arr = new long[n + 1][];
}

我敢打赌,性能影响非常小。

答案 1 :(得分:0)

您确定需要对此进行优化吗?您是否已分析运行代码并发现单锁太昂贵了?

答案 2 :(得分:0)

或使用新的Java并发API http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReadWriteLock.html重写代码 并且只在真正需要时才获得写锁定。

答案 3 :(得分:0)

鉴于您在代码中性能非常关键的部分使用它,我建议放弃延迟初始化的想法,因为它需要对每次访问系数进行几次额外的比较。

相反,我要求您的库的用户手动指定在初始化时需要多少系数。或者,我预先计算的次数超过用户可能需要的数量 - 您可以将所有nCk都适合n&lt; 1000到1 MB的内存。

PS:我建议您使用递归公式来计算系数吗?

c[n][k] = c[n-1][k-1] + c[n-1][k]

这不重要,但为什么在需要Pascals Triangle时使用复杂的公式?

答案 4 :(得分:0)

我看起来像是在计算结果时构建结果的缓存,因此您可以使用并发映射来通过构建将2个int值组合成单个long的键来保存结果。

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public final class Util {
    /**
     * Static array of nCr values
     */
    private static final ConcurrentMap<Long,Long> CACHE = 
        new ConcurrentHashMap<Long, Long>();

    /**
     * Calculate binomial coefficient (n k)
     * 
     * @param n
     *            n
     * @param k
     *            k
     * @return n choose k
     */
    public static long nCr(int n, int k) {
        if (k < 0)
            throw new ArithmeticException("Cannot choose a negative number");
        if (n < 0) {
            if (k % 2 == 0)
                return nCr(-n + k - 1, k);
            else
                return -nCr(-n + k - 1, k);
        }

        if (k > n)
            return 0;
        if (k > n / 2)
            k = n - k;

        final long key = (n << 32L) + k;

        Long value = CACHE.get(key);
        if (value != null) {
            return value.longValue();
        } 

        long result;

        if (k == 0)
            result = 1;
        else
            result = nCr(n, k - 1) * (n - (k - 1)) / k;

        CACHE.put(key, result);

        return result;
    }
}

答案 5 :(得分:0)

我最终只是让它不是静止的。如果一个线程需要获得nCr值,它会创建一个新的Coefficient对象并保留它。

答案 6 :(得分:0)

原始代码的竞争条件太多了。对于初学者,你不能更新非易失性nCr_arr,并希望双重检查成语能够正常工作。 声明它不稳定完全失去了缓存的目的。除了CAS,正确的代码不应该使用同步。

CHM在这里也是一个非常糟糕的选择(CHM也不能很好地扩展)。 (同样使用Long作为键是不是很好b / c如何valueOf工作,它不能被热点正确内联,因为它不总是创建一个对象,最终值字段也没有帮助)

如果有人(仍然)对如何编写代码感兴趣,请删除备注。 干杯