【问题标题】:volatile synchronized combination for performance用于性能的 volatile 同步组合
【发布时间】:2012-07-25 17:59:30
【问题描述】:

使用同步时会影响性能。 volatile 可以与 synchronized 结合使用以减少性能开销吗?例如,Counter 的实例将在多个线程之间共享,并且每个线程都可以访问 Counter 的公共方法。在下面的代码中,volatile 用于 getter,synchronized 用于 setter

public class Counter
{
    private volatile int count;

    public Counter()
    {
        count = 0;
    }

    public int getCount()
    {
        return count;
    }

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

请让我知道这可能会在哪种情况下中断?

【问题讨论】:

  • 我会说不。因为这两者没有关系(以你似乎认为的方式)。另外,在你的代码中,当你进入方法时,你已经准备好获得监视器锁了。
  • 是的,setter 获得了锁,但 getter 没有获得。
  • 但是你没有在getter中修改count,这只确保每次都检查值而不是缓存
  • 在这种情况下它会起作用,但是如果您的同步写入方法正在对状态执行多个操作,使得状态得到更新但在此类操作期间不一致,那么这将不起作用。例如。如果您在 increment() 中执行了两次 count++,getCount() 可能会返回第一个增量,然后在后续调用中返回下一个增量,这可能不适合您的业务逻辑。

标签: java concurrency volatile synchronized


【解决方案1】:

同时使用两者不会带来性能提升。 Volatile 通过防止缓存在并行执行的线程中读取/写入变量时保证变量的值将保持一致。 Synchronized,当应用于方法时(如您在示例中所做的那样),一次只允许单个线程进入该方法并阻止其他线程直到执行完成。

【讨论】:

  • 看Volatile的时候,我的理解是,它不支持互斥锁。它只确保所有线程将从主内存中读取易失性数据,并且不会将其缓存在任何级别。并且任何更新易失性数据的线程都会立即反映在主存储器中。请检查此link
  • 易失性不需要任何类型的全局锁。
  • 你是对的,我阅读/链接的关于 volatile 的文章很糟糕,所以我删除了它,阅读了更多关于 volatile 的内容,并更新了我的答案中的文本。感谢您的提醒。
【解决方案2】:

是的,你绝对可以。事实上,如果你查看AtomicInteger 的源代码,它本质上就是他们所做的。 AtomicInteger.get 只返回value,即volatile int (link)。与您所做的和他们所做的唯一真正的区别是他们使用 CAS 进行增量而不是同步。在现代硬件上,CAS 可以消除任何互斥;在较旧的硬件上,JVM 会在增量周围放置某种互斥锁。

易失性读取与非易失性读取的速度差不多,因此读取速度会非常快。

不仅如此,volatile 字段也保证不会撕裂:参见 JLS 17.7,它指定 volatile longs 和 doubles 不受单词撕裂的影响。因此,您的代码可以与 longint 一起使用。

正如 Diego Frehner 指出的那样,如果您在增量发生时“正确”地获取值,您可能看不到增量的结果——您将看到之前或之后。当然,如果get 已同步,您将在读取线程中获得完全相同的行为——您将看到增量前或增量后的值。所以无论哪种方式都是一样的。换句话说,说你不会看到它正在发生的价值是没有意义的——除非你的意思是撕裂,(a)你不会得到,(b)你永远不会想要。

【讨论】:

  • 感谢您提供详细信息。从同步 getter 到 AtomicInteger 确实没有区别,因为同步不保证 fifo 访问。我们应该补充一点,如果您永远不必读取旧值(例如增加银行帐户),那么您需要另一个解决方案。
  • 如果易失性读取与非易失性读取“差不多”,您为什么不直接进入主内存进行所有操作?
  • (a) 因为它们差不多快,但不完全一样快,(b) 因为易失性写入往往更慢。不过,这是特定于平台的事情。我相信在大多数机器上,写入需要更长的时间,但读取往往很便宜。我想如果你知道自从你上次从主内存读取到缓存后没有写入 volatile,那么你仍然可以使用缓存版本——不过,对此并不乐观。
【解决方案3】:

1.我个人使用过volatile这个机制结合synchronized

2.可以单独使用synchronized,并且您将始终获得一致的结果,但使用 only volatile 单独不会总是产生相同的结果。

3. 这是因为 volatile 关键字不是同步原语。 它只是阻止缓存线程上的值,但它不会阻止两个线程同时修改相同的值并将其写回。

4. volatile 允许在没有锁的情况下同时访问线程,但随后使用synchronized 将允许只有一个线程可以访问它并且类中的所有同步方法。

5. 并且 同时使用 volatile and synchronized 会做到这一点....

volatile- 会将更改的值反映到线程,并防止缓存,

synchronized - 但是使用 synchronized 关键字,将确保只有一个线程可以访问该类的同步方法。

【讨论】:

  • 需要强调的是synchronized在这个上下文中的目的是只允许一个线程执行读/写(增量)组合;访问这个词有点太笼统了。
  • @Richard 认为你错过了第四点,我强调了only one thread to get access to this and all the synchronized methods in the class.
  • @ Kumar,实际上我是直接针对第四点说的,特别是那句话。我以为你可以加强它。 “获取访问权限”可能暗示“获取”或“读取访问权限” - 有点模棱两可?不?我认为您在大多数情况下都非常清晰和简洁,所以我之前确实对您的回复投了赞成票。
  • 在同步中,它对该对象的完整读写访问同步方法,其锁已被该线程获得。
【解决方案4】:

在调用 getCount() 时,您不会总是得到最实际的计数。 AtomicInteger 可能适合您。

【讨论】:

  • 能否请您告诉我什么时候无法获得最新值的情况。
  • 当线程 A 调用 getCount() 而线程 B 试图获得排他锁时。
  • 也许我应该在线程 C 具有独占增量锁时添加。我的意思是在所描述的方式中你没有保证的读写顺序,但你总是得到最后的写入结果。这取决于您的用例您需要什么。
  • 我的场景是当任何写入线程 W1,W2...Wn 递增计数器时,它应该立即反映到读取线程 R1,R2...Rn。在这种情况下,与在 getter 中使用同步相比,上述代码是否不是一个有效的解决方案?请让我知道你的想法。
  • 如果您只需要比上述解决方案或 AtomicInteger 工作的最实际的书面值。 yshavits的答案解释得很好:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-13
  • 2017-03-13
  • 1970-01-01
  • 1970-01-01
  • 2011-08-22
  • 2014-05-16
  • 1970-01-01
相关资源
最近更新 更多