1

假设有很多线程调用该方法m(int i)并改变 position 中数组的值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();
    }
}
4

1 回答 1

11

代码是正确的,我没有看到任何竞争条件,尽管两者都a应该s进行final每次使用需要获取和释放的锁时,还应该使用 try/finally :

s[i].acquire();
try {
   a[i]++;
} finally {
   s[i].release();
}

但是,对于更新数组,每个项目单独锁定的想法是非常不必要的。单个锁同样适用,因为主要成本是内存更新和其他本机同步。这就是说,如果实际操作不是a ,int ++那么您就可以使用 aSemaphore或其他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();
}

编辑:

我刚刚编写了一个快速愚蠢的测试程序,它比较单个synchronized锁、一个synchronizedAtomicInteger数组、数组和Semaphore数组。结果如下:

  • synchronizedint[]10617 毫秒
  • synchronizedObject[]在1827ms的数组上
  • AtomicInteger数组 1414ms
  • Semaphore数组 3211ms

但是,更重要的是,这是10 个线程,每个线程执行1000 万次迭代。当然它更快,但除非您真正进行数百万次迭代,否则您不会在应用程序中看到任何明显的性能改进。这就是“过早优化”的定义。您将为代码复杂性、增加错误的可能性、增加调试时间、增加维护成本等付出代价。引用 Knuth 的话:

我们应该忘记小的效率,比如大约 97% 的时间:过早优化是万恶之源。

现在,正如 OP 在评论中暗示的那样,这i++不是她/他正在保护的真正操作。如果增量更耗时(即如果阻塞增加),则需要锁数组。

于 2012-09-11T14:49:16.073 回答