当前位置: 首页 > 面试题库 >

在Java中,AtomicInteger compareAndSet()和synced关键字的性能如何?

倪灿
2023-03-14
问题内容

我实现了请求实例的FIFO队列(为速度预先分配了请求对象),并开始在add方法上使用“
synchronized”关键字。该方法很短(检查是否在固定大小的缓冲区中有空间,然后将值添加到数组中)。使用visualVM,似乎线程阻塞的次数比我喜欢的要多(确切地说,是“监视器”)。因此,我将代码转换为将AtomicInteger值用于诸如跟踪当前大小之类的事情,然后在while循环中使用compareAndSet()(就像AtomicInteger在内部对诸如gainAndGet()之类的方法一样)。该代码现在看起来更长了。

我想知道的是,使用同步和较短的代码与不使用synced关键字的较长的代码(因此永远不要在锁上阻塞)的性能开销是多少?

这是带有synced关键字的旧get方法:

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

这是不带有synced关键字的新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];
        }
    }
}

我的猜测是,由于代码较短,但由于存在锁阻塞(可能导致线程上下文切换等)的风险,所以synced关键字更糟。

谢谢!


问题答案:

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

是的,在通常情况下,您是对的。Java Concurrency in
Practice
在15.3.2节中对此进行了讨论:

在高竞争级别上,锁定倾向于胜过原子变量,但是在更现实的竞争级别上,原子变量胜过锁定。这是因为锁通过挂起线程来对竞争做出反应,从而减少了CPU使用率和共享内存总线上的同步流量。(这类似于在生产者-消费者设计中阻止生产者如何减轻消费者的负担,从而使他们追赶。)另一方面,利用原子变量,争用管理被推回到调用类。像大多数基于CAS的算法一样,AtomicPseudoRandom通过立即重试对竞争做出反应,这通常是正确的方法,但在竞争激烈的环境中只会创造更多竞争。

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系统上成功,除非在不太可能的情况下,在读-
修改-写操作的中间抢占了线程,这种情况除外。 )

(在文本引用的图上,图15.1显示,当争用较高时,AtomicInteger和ReentrantLock的性能大致相等,而图15.2显示,在中等争用下,前者的性能要比后者高2-3倍)

更新:非阻塞算法

正如其他人指出的那样,非阻塞算法虽然可能更快,但更复杂,因此更难正确。JCiA第15.4节的提示:

良好的非阻塞算法可用于许多常见的数据结构,包括堆栈,队列,优先级队列和哈希表,尽管设计新的非阻塞算法是专家的最佳任务。

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



 类似资料:
  • 问题内容: 当我们在Java中使用关键字时,到底使用了哪个同步原语?? 编辑: JVM如何在本机级别实现锁? 问题答案: 在字节码级别上,Java具有和操作,在Java虚拟机规范的此页中进行了记录,并在下面粘贴了摘录( objectref 是该操作的操作数,取自堆栈): 监控 代码段 每个对象都有一个与其关联的监视器。执行 monitorenter 的线程 获得与 objectref 关联的监视器

  • 问题内容: 我从一开始就阅读Java教程,并且对字段或变量上的关键字有疑问。如这里所说: 类变量是使用static修饰符声明的任何字段;这告诉编译器,无论该类被实例化了多少次,该变量确实存在一个副本。可以将定义特定类型自行车的齿轮数的字段标记为静态,因为从概念上讲,相同数量的齿轮将应用于所有情况。 这样,我想如果您有一个对象(在这种情况下,该类是一个实例),并且在其中有一个字段,则与您是否喜欢or

  • 问题内容: 在Java中,我们看到了很多可以使用该关键字但很少使用的地方。 例如: 在上述情况下,可以,但是通常不这样做。 当一个方法永远不会被覆盖时,我们可以使用关键字。类似地,对于不会被继承的类。 在任何或所有这些情况下使用final关键字是否真的可以提高性能?如果是这样,那又如何?请解释。如果对性能的正确使用确实很重要,那么Java程序员应该养成什么习惯来充分利用关键字? 问题答案: 通常不

  • 我正在尝试使用google adwords api检索adwords中的单个关键字/查询性能报告,但当我运行我的查询时,我只得到来自我们购物活动的查询结果,我不知道为什么会这样。我使用的report_query如下所示 有人知道为什么会这样吗?谢谢你的帮助。

  • 问题内容: 我知道编程的含义以及何时使用它。Java中有关键字吗?我试图找到一个关键字列表,但只有而没有。 问题答案: foreach不是Java关键字(IDE可以识别它并将“ For- each ”循环放入)。

  • 问题内容: Java是否具有在休眠状态下打开会话时可以使用的using语句? 在C#中,它类似于: 因此,对象超出范围并自动关闭。 问题答案: Java 7引入了自动资源块管理,该功能将该功能引入了Java平台。Java的早期版本没有任何相似之处。 例如,您可以使用通过以下方式实现的任何变量: 由流实现的Java 接口自动扩展,因此您可以像在C#块中使用流一样使用块中的流。这等效于C#的。 从5.