我正在尝试使用双重检查锁定来维护二项式系数数组,但我最近读到双重检查锁定不起作用。效率非常重要,因此使用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];
}
}
答案 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工作,它不能被热点正确内联,因为它不总是创建一个对象,最终值字段也没有帮助)
如果有人(仍然)对如何编写代码感兴趣,请删除备注。 干杯