当前位置: 首页 > 知识库问答 >
问题:

同步块可以比原子更快吗?

柳修为
2023-03-14

假设以下两种计数器实现:

class Counter {
  private final AtomicInteger atomic = new AtomicInteger(0);
  private int i = 0;

  public void incrementAtomic() {
    atomic.incrementAndGet();
  }

  public synchronized void increment() {
    i++;
  }
}

乍一看,原子应该更快、更具可扩展性。我相信他们是的。但是它们始终比同步块快吗?或者当该规则被打破时,存在某些情况(例如SMP/单CPU机器、不同的CPU ISA、操作系统等)?

共有3个答案

鄢英毅
2023-03-14

它依赖于实现——因此最终需要在特定的平台/JVM/配置上进行基准测试。

话虽如此,出于以下原因,原子组学应始终更快:

  • 原子的设计使JVM可以利用原子机器指令,这是大多数平台上最快的原子操作
  • synchronized使用了相对较重的锁定方案和监视器对象,旨在保护潜在的大块代码。这种形式的锁定本质上比原子操作更复杂,因此您可能期望它具有更高的运行时成本
雍飞雨
2023-03-14

或者违反此规则时存在某些情况(例如SMP/单CPU机器、不同的CPU ISA、OS'es等)?

我不知道。(如果有人知道一个具体的反例,我随时准备被纠正。)

然而(这是我的主要观点)没有理论上的理由可以解释为什么您不能拥有硬件架构或实现不佳的JVM,其中同步的速度与原子类型相同或更快。(这两种同步形式的相对速度是一个实现问题,因此只能针对现有实现进行量化。)

当然,这并不意味着你永远不应该使用synchronized。同步结构有许多原子类没有解决的用例。

马胜泫
2023-03-14

增量AndGet很可能被实现为CAS循环。对于n个线程,在可能导致n-1个线程失败导致O(n)问题的高度满足的情况下。

(对于@Geek:

通常getAnd增量可以实现如下内容:

 int old;
 do {
     old = value;
 } while (!compareAndSet(value, old, old+1));
 return old;

想象一下,你有n个线程在同一个原子上执行这段代码,它们碰巧是步调一致的。第一次迭代kn起作用。只有一个CAS会成功。其他n-1个线程会重复这个练习,直到只剩下一个。所以总的工作是O(n^2)(最坏的情况)而不是O(n)。)

话虽如此,获得一个锁最多需要做一些类似的事情,而锁在激烈竞争时并不处于最佳状态。在使用CAS循环之前,您不太可能看到锁的优势,因为CAS循环需要在get和compareAndSwap之前进行大量计算。

 类似资料:
  • 问题内容: 我已经开始学习线程同步。 同步方法: 同步块: 什么时候应该使用方法和块? 为什么块比方法更好? 问题答案: 这不是更好的问题,只是有所不同。 同步方法时,实际上是在与对象本身进行同步。对于静态方法,您正在同步到对象的类。因此,以下两段代码以相同的方式执行: 就像您写的一样。 如果要控制到特定对象的同步,或者只想将方法的 一部分 同步到该对象,则指定一个块。如果在方法声明上使用关键字,

  • 据我所知,synchronized关键字将本地线程缓存与主存同步。volatile关键字基本上总是在每次访问时从主存中读取变量。当然,访问主存比本地线程缓存要昂贵得多,因此这些操作也很昂贵。然而,CAS操作使用低级硬件操作,但仍然必须访问主存。那么CAS操作如何更快呢?

  • 基于https://logging.apache.org/log4j/2.x/manual/async.html我想使用混合同步和异步记录器的方法,以便从所有同步记录器的性能改进中获益。 基准代码: Log4j2配置正是文档中的配置(https://logging.apache.org/log4j/2.x/manual/async.html): 使用这种混合的同步/异步记录器配置,我可以每秒获得大

  • Solr4提供了对索引中现有文档进行原子(部分)更新的功能。即。可以匹配文档ID并替换一个字段的内容,或者向多值字段添加更多条目:http://wiki.apache.org/solr/atomicupdates 原子更新可以从DataImportHandler(DIH)完成吗?

  • 我知道下一个场景:(奇怪的格式,我知道) 如果线程#1和线程#2在完全相同的时间输入,这将发生: > 两者都将执行" CMPXCHG指令同时对两个线程生效: 3.1锁定前缀本机使用 3.2线程#1或#2首先到达,赢得比赛。 3.3获胜线程比较(是aBoolean==true?)这将返回"true",因此一个布尔值将被设置为"false"。 3.4 aBoolean现在为false。 3.5线程丢失

  • 问题内容: 这个问题一再被问到,但我仍然有疑问。当人们说同步创建了一个内存障碍时,这个内存障碍适用于什么缓存变量?这看起来不可行。 因此,由于这个疑问,我编写了一些看起来像这样的代码: 我想知道是否有可能只用简单的double []代替total的类型:这将要求synced(总计)(在run()方法中)确保我不使用索引中的每个索引的本地副本双精度数组,即内存围栏不仅适用于自身的值(在指针的背后),