代码是正确的,我没有看到任何竞争条件,尽管两者都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
锁、一个synchronized
锁AtomicInteger
数组、数组和Semaphore
数组。结果如下:
synchronized
在int[]
10617 毫秒
synchronized
Object[]
在1827ms的数组上
AtomicInteger
数组 1414ms
Semaphore
数组 3211ms
但是,更重要的是,这是10 个线程,每个线程执行1000 万次迭代。当然它更快,但除非您真正进行数百万次迭代,否则您不会在应用程序中看到任何明显的性能改进。这就是“过早优化”的定义。您将为代码复杂性、增加错误的可能性、增加调试时间、增加维护成本等付出代价。引用 Knuth 的话:
我们应该忘记小的效率,比如大约 97% 的时间:过早优化是万恶之源。
现在,正如 OP 在评论中暗示的那样,这i++
不是她/他正在保护的真正操作。如果增量更耗时(即如果阻塞增加),则需要锁数组。