【问题标题】:Java synchronized counter - what about get()?Java 同步计数器 - get() 呢?
【发布时间】:2012-09-25 17:40:03
【问题描述】:

众所周知,简单的x++不是原子操作,实际上是读-增-写操作。这就是为什么它应该同步。但是get() 呢?我读过它也应该同步,但有人能解释一下为什么吗?通过引入happens-before关系来避免内存一致性错误? 当get() 经常被多个线程调用并且值很少更改时,情况会怎样。 synchronized get() 不是让他们慢下来吗?在这种情况下是否有其他方法可以实现同步(不使用 AtomicInteger)? volatile 关键字在这里有用吗?

public class Counter {
    private int value;

    public synchronized int get() { return value; }
    public synchronized int increment() { return ++value; }
    public synchronized int decrement() { return --value; }
}

谢谢!

编辑:

我想澄清一下。通过使用volatile,我的意思是在get() 方法中引入该关键字并删除synchronized。我想知道它是否会使其成为线程安全的,但如果许多线程正在读取该值并且一个很少更改它,那么它是否会更有效。

【问题讨论】:

    标签: java multithreading synchronization counter


    【解决方案1】:

    在那种情况下有没有其他方法可以实现同步(不使用 AtomicInteger)?

    首先,如果可以,您应该使用AtomicInteger。我不知道你为什么不使用它。

    volatile 关键字在这里有用吗?

    是的,++ 除外。 AtomicInteger 提供了一个没有锁定的安全增量。如果您想自己滚动(出于某种疯狂的原因),那么您将需要阻塞,或者您需要复制 AtomicInteger 内部自旋机制。

    但是 get() 呢?我读过它也应该同步,但有人能解释一下为什么吗?

    AtomicInteger 包装 volatile int 以提供其功能。当您访问volatile 字段时,您也会越过get() 上的内存屏障。您需要跨越该内存屏障以确保如果另一个线程更新了值,调用get() 的线程会看到更新。否则,线程可能正在使用过时的值。

    【讨论】:

    • 我只是想实现与不使用它的 AtomicInteger 相同的行为:P 通过使用 volatile 我的意思是在 get() 方法中引入该关键字并删除 synchronized。我想知道如果许多线程读取该值并且一个很少更改它,它是否会使其成为线程安全但也更有效。
    • 是的@JakubKrawczyk,那行得通。你想比AtomicInteger 更快吗?为什么不使用那个类?如果这是为了提高效率,那么您正在定义过早优化。
    • 我不想实现我自己的AtomicInteger :) 我只是想了解这件事上的同步。我认为显而易见的答案是“使用AtomicInteger”,我想避免它:P 它没有用;)谢谢!
    • @Gray 线程调度程序是否有可能更改已获取锁并正在执行同步块的线程的状态以说等待以便其他线程可以获取锁?
    • 线程是否有锁。一个线程当然可以拥有锁并且不会被调度,但是如果这就是你所暗示的@YugSingh,那么一个线程将不会从它身上拿走锁。
    【解决方案2】:

    如果你反复读取一个非易失性值,JVM 可以将该值缓存在一个寄存器中,你可能看不到任何变化。

    避免这种情况的方法是使值不稳定。

    但是,如果性能是一个问题,请使用无锁的 AtomicInteger。

    【讨论】:

    • 我指的是 CPU 的寄存器,但在缓存级别也会遇到类似的问题。
    • “可能看不到任何更改”比 Java 文档中所说的“同步建立之前发生的关系”更清楚。有时,如果获取方仍在轮询,您并不关心订单,但您当然确实关心最终获得更改。从您的评论中可以看出,获得方可能永远看不到变化。
    【解决方案3】:

    但是 get() 呢?我读过它也应该同步,但有人能解释一下为什么吗?通过引入happens-before关系来避免内存一致性错误?

    是的,它应该是同步的。如果不是,它可能使用了一个陈旧的值,就像增量/减量一样。

    如果 get() 经常被多个线程调用并且值很少更改,该怎么办?同步的 get() 不会减慢它们的速度吗?

    无竞争锁非常便宜。即使对于并发获取,成本也可能不会对整体性能产生太大影响。 volatile 在这里工作,但出于性能原因不使用 AtomicInteger 可能没有必要。

    在这种情况下是否有其他方法可以实现同步(不使用 AtomicInteger)? volatile 关键字在这里有用吗?

    是的,我相信会的。

    【讨论】:

      【解决方案4】:

      但是 get() 呢?我读过它也应该同步,但是 谁能解释一下为什么?

      假设如果get() 没有同步,那么任何线程都可以在线程执行set() 操作时调用它..

      现在,如果假设线程 B 正在执行 set() 操作以将字段 a 的值设置为 5。现在,假设两个线程正在读取值 a.. 一个线程在 set 操作完成之前读取, 和其他线程在设置操作后读取..

      因此,两个线程具有不同的值。因此它们的字段a 的状态不一致。

      现在假设get()方法是同步的。那么如果一个线程正在设置一个值。那么除非set()操作完成,否则没有线程可以调用get()操作。所以每个线程都会得到相同的价值..

      【讨论】:

      • 一些应用程序不需要每个线程尽快看到相同的值,只要它们最终看到正确的值。 @Peter Lawreys 的评论表明此类应用程序仍然存在风险。
      • 线程调度器是否有可能改变一个已经获得锁并正在执行同步块的线程的状态,说等待,以便其他线程可以获得锁?
      猜你喜欢
      • 1970-01-01
      • 2016-02-19
      • 1970-01-01
      • 2021-06-27
      • 1970-01-01
      • 1970-01-01
      • 2022-10-25
      • 2011-11-30
      • 1970-01-01
      相关资源
      最近更新 更多