使用信号量保护阵列

时间:2012-09-11 14:46:37

标签: java multithreading semaphore

假设有许多线程调用方法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();
    }
}

1 个答案:

答案 0 :(得分:11)

代码是正确的,虽然as都应该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锁,一个锁数组synchronizedAtomicInteger数组和Semaphore数组。结果如下:

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

,踢球者认为这是 10个线程,每次 1000万次次迭代。当然它更快但除非您真正进行了数百万次迭代,否则您的应用程序中不会看到任何明显的性能提升。这是"premature optimization"的定义。您支付代码复杂性,增加错误的可能性,增加调试时间,增加维护成本等。引用Knuth:

  

我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。

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