一个同步块与多个AtomicInteger递增的比较

3
我明白使用AtomicInteger来递增共享的int值比使用synchronized块更好。但是,对于多个int值呢?

以下哪种方法更好?为什么?是否有更好的方法来提高性能?

1) 使用同步块:

int i, j, k, l;
public void synchronized incrementValues() {
    i++;j++;k++;l++;
}

2) 使用AtomicInteger:


AtomicInteger i,j,k,l;
// Initialize i, j, k, l

public void incrementValues() {
    i.incrementAndGet();
    j.incrementAndGet();
    k.incrementAndGet();
    l.incrementAndGet();
}

如果我使用 ReentrantLock ,那会更快吗?

3) 使用ReentrantLock:

ReentrantLock lock = new ReentrantLock()
int i, j, k, l;
public void incrementValues() {
    lock.lock();
    try {
        i++;j++;k++;l++;
    } finally {
        lock.unlock();
    }
}

以下是我的问题:

  1. 3是否是最快的?
  2. 2呢?对于单个整数,2比1更快。如果整数数量增加,2会变得比1慢吗?

编辑1基于Matthias的答案修改问题。

i、j、k、l彼此独立。各自的增量应该是原子的,而不是整个的。如果线程2在线程1修改k之前修改了i,则可以。

编辑2根据迄今为止的评论提供的附加信息。

我并不要求得到确切的答案,因为我理解这取决于函数的使用方式和争用的数量等,并且针对每个用例进行测量是确定确切答案的最佳方法。但是,我希望看到人们分享他们的知识/文章等,以阐明影响情况的参数/优化。感谢@Marco13的文章。它很有启发性。


AtomicIntegerArray 也是不错的选择。我想我会选择 synchronized,因为它是最简单的,而且在这种情况下可能是最好的。 - Bubletan
1
如果您真正关心性能,就要进行测试。如果您无法测量到有意义的差异,那么就使用最简单的方法。 - Chris Dodd
这确实是一个有趣的问题,但很难回答。人们需要进行详细的性能测试,例如使用JMH,并将应用于4个变量(就像您的情况)的结果与使用1...n个变量(其中“n”可以是synchronized变得更快的点)的结果进行比较 - 即使是这样,您也很难知道像锁消除或粗化(http://www.ibm.com/developerworks/library/j-jtp10185/index.html)这样的事情是否扭曲了结果变得毫无意义... - Marco13
我更新了以下回答以回答你的新问题。 - Matthias
1个回答

2

首先,#2不是线程安全的。incrementAndGet()是原子性的,但是连续调用四个incrementAndGet操作不是。(例如,在第二个incrementAndGet之后,另一个线程可以进入相同的方法并开始执行类似下面示例的操作。

T1: i.incrementAndGet();
T1: j.incrementAndGet();
T1: k.incrementAndGet();
T2: i.incrementAndGet();
T2: j.incrementAndGet();
T1: l.incrementAndGet();
T2: k.incrementAndGet();
T2: l.incrementAndGet();

然后,如果它在#1和#3之间:如果您不热衷于高速股票交易,则这对您无关紧要。可能会有非常小的差异(在仅为整数的情况下可能是纳秒级),但这并不重要。然而,我总是会选择#1,因为它更简单,也更安全(例如,想象一下您忘记在finally块中放置unlock() - 那么您可能会遇到大麻烦)。
关于您的编辑: 对于第1点:有时候同时原子修改多个值可能很重要。考虑数据不仅被递增,而且同时被读取的情况。您会认为在任何时间点,所有变量都具有相同的值。然而,由于更新操作不是原子性的,当您读取数据时,可能是I = j = k = 5,l = 4,因为执行递增的线程尚未到达最后一个操作。 是否存在此问题非常取决于您的问题。如果您不需要此类保证,则不必担心。
对于第2点: 优化很难,而并发更加困难。我只能建议不要考虑这些微小的优化。在最好的情况下,这些优化可以节省纳秒级,但使代码非常复杂。在最坏的情况下,优化存在错误假设或逻辑错误,您将遇到并发问题。但最有可能的是,您的优化表现会更差。 还要考虑到您编写的代码可能需要由其他人在以后的某个时间点进行维护。而您在编程执行中节省的毫秒数却浪费了处理器的生命,因为它正在尝试理解您想要做什么以及为什么要这样做,同时尝试修复那个讨厌的多线程错误。 所以,为了简单起见:synchronized是最好的选择。
KISS原则对于并发确实非常重要。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接