代码之家  ›  专栏  ›  技术社区  ›  Dr. Hans-Peter Störr

字段读取和volatile同步之间的区别

  •  10
  • Dr. Hans-Peter Störr  · 技术社区  · 14 年前

    在一个好的 article with some concurrency tips ,将一个示例优化为以下行:

    double getBalance() {
        Account acct = verify(name, password);
        synchronized(acct) { return acct.balance; }
    }
    

    如果我理解正确,那么同步的要点是确保此线程读取的acct.balance的值是最新的,并且对acct.balance中对象的字段的任何挂起的写入也会写入主内存。

    这个例子让我思考了一下:仅仅声明acct.balance(即类Account的字段balance)为 volatile ? 它应该更有效,节省你所有的时间 synchronize 在访问acct.balance时,不会锁定整个 acct

    3 回复  |  直到 14 年前
        1
  •  14
  •   Mike Q    14 年前

    你说得对。volatile提供了可见性保证。synchronized提供了可见性保证和受保护代码段的序列化。对于非常简单的情况,volatile就足够了,但是使用volatile而不是同步很容易陷入麻烦。

    如果你假设账户有调整余额的方法,那么volatile就不够好了

    public void add(double amount)
    {
       balance = balance + amount;
    }
    

    Thread1 - Calls add(100)
    Thread2 - Calls add(200)
    Thread1 - Read balance (0)
    Thread2 - Read balance (0)
    Thread1 - Compute new balance (0+100=100)
    Thread2 - Compute new balance (0+200=200)
    Thread1 - Write balance = 100
    Thread2 - Write balance = 200 (WRONG!)
    

    显然这是错误的,因为两个线程都读取当前值并独立更新,然后将其写回(读、计算、写)。volatile在这里没有帮助,因此您需要同步以确保一个线程在另一个线程开始之前完成整个更新。

    我发现,如果在编写一些代码时,我认为“我可以使用volatile而不是synchronized吗”,答案很可能是“是的”,但是花时间/精力去弄清楚这一点以及出错的危险是不值得的(性能很小)。

        2
  •  1
  •   Sudhakar Kalmari    14 年前

    将帐户声明为易失性将受到以下问题和限制

    1.“由于其他线程看不到局部变量, 此外,如果您试图在一个方法中声明一个volatile变量,在某些情况下您会得到一个编译器错误。

    双getBalance(){ }

    1. 每次都将它们重新获取,而不是将它们缓存在寄存器中 . 这也是 假设没有其他线程会意外地更改值。

    2. 如果需要同步以协调来自不同线程的变量更改, ,因为访问volatile变量从不持有锁,所以它不适合作为原子操作进行读更新写的情况。除非您确定acct=verify(name,password);是单原子操作,你不能保证例外的结果

    3. 如果变量acct是一个对象引用,那么它很可能为空。 尝试在空对象上同步将引发NullPointerException 使用同步的。 (因为您正在有效地同步引用,而不是实际对象) 哪里不抱怨

    4. 私有volatile boolean someAccountflag;

      public void getBalance(){ 账户; acct=验证(名称、密码); } }

    不能在具有synchronized的基元上同步, synchronized只适用于对象变量,其中as原语或对象变量可以声明为volatile

    6. JVM解决了这个问题。因此,如果someAccountflag是最终静态的,那么它甚至不需要声明为volatile 或者您可以使用懒惰的单例初始化,将Account作为单例对象 并声明如下:

        3
  •  1
  •   Community Dunja Lalic    7 年前

    如果多个线程正在修改和访问数据, synchronized 保证多线程之间的数据一致性。

    如果单个线程正在修改数据,而多个线程尝试读取数据的最新值,请使用 volatile 构造。

    但对于上述代码, 不稳定的 AtomicReference 具有 Double 类型符合你的目的。

    相关问题:

    Difference between volatile and synchronized in Java