在Java中,AtomicInteger compareAndSet()与synchronized关键字的性能如何? [英] In Java what is the performance of AtomicInteger compareAndSet() versus synchronized keyword?

查看:160
本文介绍了在Java中,AtomicInteger compareAndSet()与synchronized关键字的性能如何?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在实现请求实例的FIFO队列(预分配的请求对象以提高速度),并在add方法上使用synchronized关键字开始。该方法非常短(检查固定大小缓冲区中的空间,然后向数组添加值)。使用visualVM看起来线程比我喜欢的更频繁地阻塞(监视器是精确的)。所以我将代码转换为使用AtomicInteger值,例如跟踪当前大小,然后在while循环中使用compareAndSet()(因为AtomicInteger在内部为incrementAndGet()等方法执行)。代码现在看起来要长一点。

I was implementing a FIFO queue of requests instances (preallocated request objects for speed) and started with using the "synchronized" keyword on the add method. The method was quite short (check if room in fixed size buffer, then add value to array). Using visualVM it appeared the thread was blocking more often than I liked ("monitor" to be precise). So I converted the code over to use AtomicInteger values for things such as keeping track of the current size, then using compareAndSet() in while loops (as AtomicInteger does internally for methods such as incrementAndGet()). The code now looks quite a bit longer.

我想知道使用synchronized和更短代码的性能开销与没有synchronized关键字的更长代码的性能开销是什么(所以应该永远不会阻止锁定。)

What I was wondering is what is the performance overhead of using synchronized and shorter code versus longer code without the synchronized keyword (so should never block on a lock).

这是使用synchronized关键字的旧get方法:

Here is the old get method with the synchronized keyword:

public synchronized Request get()
{
    if (head == tail)
    {
        return null;
    }
    Request r = requests[head];
    head = (head + 1) % requests.length;
    return r;
}

这是没有synchronized关键字的新get方法:

Here is the new get method without the synchronized keyword:

public Request get()
{
    while (true)
    {
        int current = size.get();
        if (current <= 0)
        {
            return null;
        }
        if (size.compareAndSet(current, current - 1))
        {
            break;
        }
    }

    while (true)
    {
        int current = head.get();
        int nextHead = (current + 1) % requests.length;
        if (head.compareAndSet(current, nextHead))
        {
            return requests[current];
        }
    }
}

我的猜测是synchronized关键字更糟糕的是因为锁定的风险(可能导致线程上下文切换等),即使代码更短。

My guess was the synchronized keyword is worse because of the risk of blocking on the lock (potentially causing thread context switches etc), even though the code is shorter.

谢谢!

推荐答案


我的猜测是synchronized关键字更糟,因为存在阻塞锁的风险(可能导致线程上下文切换等) )

My guess was the synchronized keyword is worse because of the risk of blocking on the lock (potentially causing thread context switches etc)

是的,在一般情况下你是对的。 Java Concurrency in Practice 在第15.3.2节中对此进行了讨论:

Yes, in the common case you are right. Java Concurrency in Practice discusses this in section 15.3.2:


[...]在高争用级别锁定往往优于原子变量,但在更现实的争用级别,原子变量优于锁定。这是因为锁通过挂起线程来对争用作出反应,从而减少共享内存总线上的CPU使用率和同步流量。 (这类似于生产者 - 消费者设计中的阻塞生成器如何减少消费者的负担,从而让他们赶上来。)另一方面,使用原子变量,争用管理被推回到调用类。与大多数基于CAS的算法一样, AtomicPseudoRandom 通过立即再次尝试来对争用作出反应,这通常是正确的方法,但在高争用环境中只会产生更多争用。

[...] at high contention levels locking tends to outperform atomic variables, but at more realistic contention levels atomic variables outperform locks. This is because a lock reacts to contention by suspending threads, reducing CPU usage and synchronization traffic on the shared memory bus. (This is similar to how blocking producers in a producer-consumer design reduces the load on consumers and thereby lets them catch up.) On the other hand, with atomic variables, contention management is pushed back to the calling class. Like most CAS-based algorithms, AtomicPseudoRandom reacts to contention by trying again immediately, which is usually the right approach but in a high-contention environment just creates more contention.

在我们谴责 AtomicPseudoRandom 之前,由于编写不好或原子变量与锁相比较差,我们应该意识到图15.1中的争用是不切实际的高:没有真正的程序只会争夺锁或原子变量。在实践中,原子倾向于比锁更好地扩展,因为原子更有效地处理典型的争用水平。

Before we condemn AtomicPseudoRandom as poorly written or atomic variables as a poor choice compared to locks, we should realize that the level of contention in Figure 15.1 is unrealistically high: no real program does nothing but contend for a lock or atomic variable. In practice, atomics tend to scale better than locks because atomics deal more effectively with typical contention levels.

锁和原子在不同争用水平之间的性能逆转说明了优势和每个人的弱点。由于低到中等的争用,原子提供了更好的可扩展性;在高争用的情况下,锁可提供更好的争用避免。 (基于CAS的算法在单CPU系统上也优于基于锁定的算法,因为CAS总是在单CPU系统上成功,除非在读 - 修改 - 写操作过程中线程被抢占的情况不太可能。 )

The performance reversal between locks and atomics at differing levels of contention illustrates the strengths and weaknesses of each. With low to moderate contention, atomics offer better scalability; with high contention, locks offer better contention avoidance. (CAS-based algorithms also outperform lock-based ones on single-CPU systems, since a CAS always succeeds on a single-CPU system except in the unlikely case that a thread is preempted in the middle of the read-modify-write operation.)

(在文中提到的数字,图15.1显示AtomicInteger和ReentrantLock的性能或多或少相等)争用率很高,而图15.2显示,在适度争用下,前者的表现优于后者2-3倍。)

(On the figures referred to by the text, Figure 15.1 shows that the performance of AtomicInteger and ReentrantLock is more or less equal when contention is high, while Figure 15.2 shows that under moderate contention the former outperforms the latter by a factor of 2-3.)

正如其他人所说,非阻塞算法虽然可能更快,但更复杂,因此更难以正确使用。 JCiA第15.4节的提示:

As others have noted, nonblocking algorithms, although potentially faster, are more complex, thus more difficult to get right. A hint from section 15.4 of JCiA:


许多常见的数据结构都有很好的非阻塞算法,包括堆栈,队列,优先级队列和哈希表,虽然设计新的哈希表是最好留给专家的任务。

Good nonblocking algorithms are known for many common data structures, including stacks, queues, priority queues, and hash tables, though designing new ones is a task best left to experts.

非阻塞算法比基于锁的算法要复杂得多。创建非阻塞算法的关键是弄清楚如何在保持数据一致性的同时将原子更改的范围限制为单个变量。在诸如队列之类的链接集合类中,您有时可以将状态转换表示为对单个链接的更改,并使用 AtomicReference 来表示必须以原子方式更新的每个链接。 / p>

Nonblocking algorithms are considerably more complicated than their lock-based equivalents. The key to creating nonblocking algorithms is figuring out how to limit the scope of atomic changes to a single variable while maintaining data consistency. In linked collection classes such as queues, you can sometimes get away with expressing state transformations as changes to individual links and using an AtomicReference to represent each link that must be updated atomically.

这篇关于在Java中,AtomicInteger compareAndSet()与synchronized关键字的性能如何?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆