在Java中,AtomicIntegercompareAndSet()与synchronized关键字的性能如何?
我正在实现一个请求实例的 FIFO 队列(为了速度而预先分配的请求对象),并开始在 add 方法上使用“synchronized”关键字。该方法非常短(检查固定大小缓冲区中是否有空间,然后将值添加到数组)。使用 VisualVM 时,线程阻塞的频率似乎比我喜欢的要高(准确地说是“监视”)。因此,我将代码转换为使用 AtomicInteger 值来执行诸如跟踪当前大小之类的操作,然后在 while 循环中使用compareAndSet()(就像 AtomicInteger 在内部对incrementAndGet() 等方法所做的那样)。现在代码看起来有点长。
我想知道的是,使用同步和较短的代码与不使用同步关键字的较长代码相比,性能开销是多少(因此永远不应该阻塞锁)。
这是带有synchronized关键字的旧get方法:
public synchronized Request get()
{
if (head == tail)
{
return null;
}
Request r = requests[head];
head = (head + 1) % requests.length;
return r;
}
这是不带synchronized关键字的新get方法:
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关键字更糟糕,因为存在阻塞锁的风险(可能导致线程上下文切换等),即使代码更短。
谢谢!
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.
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).
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;
}
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];
}
}
}
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.
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
是的,在常见情况下,您是对的。 Java 并发实践 在第 15.3.2 节中对此进行了讨论:
(在文中提到的数字上,图 15.1 显示,当争用较高时,AtomicInteger 和 ReentrantLock 的性能或多或少相等,而图 15.2 显示,在中等争用情况下,前者的性能优于后者 2 倍- 3.)
更新:关于非阻塞算法
正如其他人所指出的,非阻塞算法虽然可能更快,但更复杂,因此更难以正确执行。 JCiA 第 15.4 节的提示:
Yes, in the common case you are right. Java Concurrency in Practice discusses this in section 15.3.2:
(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.)
Update: on nonblocking algorithms
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:
我想知道 jvm 在真正挂起线程之前是否已经进行了一些旋转。它预计像您这样写得好的关键部分非常短并且几乎立即完成。因此,在放弃并挂起线程之前,它应该乐观地忙等待,我不知道,几十个循环。如果是这种情况,它的行为应该与您的第二个版本相同。
分析器显示的内容可能与全速运行的 jvm 中实际发生的情况有很大不同,并进行了各种疯狂的优化。最好在没有分析器的情况下测量和比较吞吐量。
I wonder if jvm already does a few spin before really suspending the thread. It anticipate that well written critical sections, like yours, are very short and complete almost immediately. Therefore it should optimistically busy-wait for, I don't know, dozens of loops, before giving up and suspending the thread. If that's the case, it should behave the same as your 2nd version.
what a profiler shows might be very different from what's realy happending in a jvm at full speed, with all kinds of crazy optimizations. it's better to measure and compare throughputs without profiler.
在进行这种同步优化之前,您确实需要一个分析器来告诉您这是绝对必要的。
是的,在某些情况下同步可能比原子操作慢,但请比较您的原始方法和替换方法。前者非常清晰且易于维护,后者则肯定更复杂。因此,可能存在非常微妙的并发错误,您在初始测试期间不会发现这些错误。我已经看到一个问题,
size
和head
确实可能不同步,因为虽然这些操作中的每一个都是原子的,但组合却不是,有时这可能会导致到不一致的状态。所以,我的建议是:
Before doing this kind of synchronization optimizations, you really need a profiler to tell you that it's absolutely necessary.
Yes, synchronized under some conditions may be slower than atomic operation, but compare your original and replacement methods. The former is really clear and easy to maintain, the latter, well it's definitely more complex. Because of this there may be very subtle concurrency bugs, that you will not find during initial testing. I already see one problem,
size
andhead
can really get out of sync, because, though each of these operations is atomic, the combination is not, and sometimes this may lead to an inconsistent state.So, my advise:
这是繁忙等待锁的代码。
Here's code for a busy wait lock.