假设有许多线程调用方法m(int i)
并更改位置i
中的数组值。以下代码是否正确,或者是否存在竞争条件?
public class A{
private int []a =new int[N];
private Semaphore[] s=new Semaphore[N];
public A(){
for(int i =0 ; i<N ; i++)
s[i]=new Semaphore(1);
}
public void m(int i){
s[i].acquire();
a[i]++;
s[i].release();
}
}
答案 0 :(得分:11)
代码是正确的,虽然a
和s
都应该final
,但我认为没有竞争条件。您还应该使用try / finally 每个时间使用需要获取和释放的锁:
s[i].acquire();
try {
a[i]++;
} finally {
s[i].release();
}
但是,为了更新数组,每个项目的单独锁定的想法是非常不必要的。单个锁是恰当的,因为主要成本是内存更新和其他本机同步。这就是说,如果实际操作不 int ++
,那么您有理由使用Semaphore
或其他Lock
对象。
但是对于简单的操作,下面的内容很好:
// make sure it is final if you are synchronizing on it
private final int[] a = new int[N];
...
public void m(int i) {
synchronized (a) {
a[i]++:
}
}
如果你真的担心阻塞,那么AtomicInteger
阵列是另一种可能性,但除非探查者告诉你,否则即使这样也有点矫枉过正。
private final AtomicInteger[] a = new AtomicInteger[N];
...
public A(){
for(int i = 0; i < N; i++)
a[i] = new AtomicInteger(0);
}
public void m(int i) {
a[i].incrementAndGet();
}
修改强>
我刚写了一个quick stupid test program来比较单个synchronized
锁,一个锁数组synchronized
,AtomicInteger
数组和Semaphore
数组。结果如下:
synchronized
在int[]
10617ms synchronized
1827毫秒的数组上Object[]
AtomicInteger
array 1414ms Semaphore
array 3211ms 但,踢球者认为这是 10个线程,每次 1000万次次迭代。当然它更快但除非您真正进行了数百万次迭代,否则您的应用程序中不会看到任何明显的性能提升。这是"premature optimization"的定义。您将支付代码复杂性,增加错误的可能性,增加调试时间,增加维护成本等。引用Knuth:
我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。
现在,正如OP在评论中所暗示的那样,i++
并不是他/她正在保护的真实操作。如果增量耗费更多(即阻塞增加),则需要锁定数组。