【问题标题】:is synchronized needed in getValue() ? & volatile needed?getValue() 中需要同步吗? & 需要挥发性的?
【发布时间】:2021-12-27 14:35:33
【问题描述】:

我有一个多线程应用类:

public class A {
        private volatile int value = 0; // is volatile needed here?
    
        synchronized public void increment() { 
          value++; // Atomic is better, agree
        }
    
        public int getValue() { // synchronized needed ?
          return value;
        }
    }

【问题讨论】:

  • “需要同步吗?” - 不,除非你让increment 更重并且不想让getValue()value 仍然存在的情况下接触value执行。 “这里需要 volatile 吗?” - 是的,否则 getValue() 可能会读取缓存的值而不是实际的实际值(阅读发生前发生的情况)。
  • 如果值是不稳定的,那么你不需要在'getValue()'上同步。如果你在 'getValue()' 上使用同步,那么你不需要 volatile,因为你所有的访问都是通过同步方法。
  • @Zabuzard,想象两个线程,一个调用increment(),另一个同时调用getValue()getValue() 调用要么返回增量之前的值,要么返回增量之后的值。 (我对吗?)。好的,现在重复实验,只是这次是synchronized int getValue() 结果有什么不同?这没有有什么不同!! getValue() 要么返回增量之前的值,要么返回增量之后的值。
  • @SolomonSlow 没错,这就是我的观点......因此,为什么它在当前状态下在 getValue() 上已过时。直到你开始让increment() 成为一个不能被“中断”的非常繁重的方法。在我对 Ravindras 答案的其他评论中有更多详细信息。
  • @Zabuzard,好的,但也许你错过了我的意思。我对你所说的做出反应,“除非你......不希望getValue()increment 仍在执行时得到value。”听起来您认为在某些情况下,将 synchronized 添加到 getValue() 方法实际上提供好处。

标签: java multithreading synchronized


【解决方案1】:

关键字volatile 为您提供可见性方面,否则您可能会看到一些陈旧的价值。易失性读取增加了内存屏障,使得编译器、硬件或 JVM 无法以违反内存模型提供的可见性保证的方式重新排序内存操作。根据内存模型,对 volatile 字段的写入发生在之后每次读取同一字段,因此您可以保证读取最新的值。

还需要关键字synchronized,因为您正在执行一个复合操作value++,它必须以原子方式完成。您读取该值,在 CPU 中将其递增,然后将其写回。所有这些动作都必须以原子方式完成。但是,您不需要synchronize 读取路径,因为关键字volatile 保证了可见性。事实上,在读取路径上同时使用 volatilesynchronize 会造成混淆,并且不会提供任何性能或安全优势。

通常鼓励使用原子变量,因为它们使用 CPU 内置的 CAS 指令进行非阻塞同步,从而产生低锁争用和更高的吞吐量。如果它是使用原子变量编写的,它会是这样的。

public class A {
    private final LongAdder value = new LongAdder();

    public void increment() {
        value.add(1);
    }

    public int getValue() {
        return value.intValue();
    }
}

【讨论】:

  • 请注意,在代码的当前状态下,synchronized 仅在 increment 方法上需要,getValue 方法上不需要。由于竞争条件,getValue() 是在更新前不久还是在更新后不久读取值无关紧要。无论如何,即使使用synchronized,您也无法控制实际方法执行的时间。
  • @Zabuzard 混合 volatile 和 synchronized 似乎更令人困惑。我的偏好是对这两种方法都使用同步并删除 volatile。这样,很明显两种方法都使用相同的锁。 (不是说其他​​方式行不通)
  • 好吧,synchronized 读取比volatile 读取要重得多,并且可能会降低读取路径中的性能。因此,在这种情况下,我宁愿选择 volatile 读取。
  • 需要synchronized 的一种情况是对于64b 值,例如longdouble,它们的分配不是原子的,这意味着不受保护的读取可能会读取损坏的值。跨度>
  • 否,如果 64 位数值变量 longdouble 被声明为 volatile 或被锁保护,根据 Java 内存模型不会出现此问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-01
相关资源
最近更新 更多