2010-06-23 57 views
10

在一个很好的article with some concurrency tips,一个例子进行了优化,以下面的行:差分读取和挥发性

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

如果我明白正确,同步点是保证的值此线程读取的acct.balance是当前值,并且任何等待写入acct.balance中的对象字段的写入也会写入主内存。

这个例子让我想起了一点:仅仅将acct.balance(即类Account的字段余额)声明为volatile不是更高效吗?它应该更有效率,在访问acct.balance时节省所有的synchronize,并且不会锁定整个acct对象。我错过了什么吗?

+0

你是正确的,但文章实际上是关于完全不同的东西 - 减少锁的范围。 – gustafc 2010-06-23 17:58:22

回答

13

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

如果你假定账户有调整其资产负债的一种方式则挥发性不够好

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

然后我们有一个问题,如果余额为挥发性没有其他的同步。如果两个线程都试图调用add()在一起,你可以有一个“失败”的更新,其中将出现以下情况

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”,答案很可能是“是”,但是确定它的时间/努力以及弄错它的危险不值得的好处(次要表现)。

另外一个写得很好的账户类将在内部处理所有的同步逻辑,因此呼叫者不必担心它。

1

声明帐户作为挥发性受到以下问题和限制

1.“由于其他线程无法看到局部变量,声明局部变量波动是徒劳。”此外,如果您尝试在方法中声明易失性变量,则在某些情况下会出现编译器错误。

double getBalance(){ volatile账户acct = verify(name,password); //错误.. }

  • 声明帐户作为挥发性警告编译器获取它们新鲜每一次,而不是在寄存器缓存它们。这也禁止某些优化,假设没有其他线程会意外地更改值。

  • 如果您需要同步协调来自不同的线程修改变量, 挥发并不能保证你原子访问,因为访问volatile变量从未持有锁,它不适合我们想要的情况下以读取更新 - 写入为原子操作。除非你确定acct = verify(name,password);是单原子操作,你不能保证例外结果

  • 如果变量acct是一个对象引用,那么它的机会可能是null。 试图在空对象上进行同步将引发使用同步的NullPointerException。 (因为你有效地同步的参考,而不是实际的对象) 凡为挥发性不抱怨

  • 相反,你可以声明一个布尔变量挥发性喜欢这里

    私人挥发性布尔someAccountflag;

    public void getBalance(){ Account acct; (!someAccountflag){ acct = verify(name,password); } }

  • 注意不能声明someAccountflag为synchronized,因为 你不能在原语同步同步,同步仅适用于对象变量,其中作为原始或对象变量可以被声明为volatile

    6. 类最终静态字段不需要是易失的,JVM负责处理这个问题。所以someAccount标志不需要甚至被声明为易变的,如果它是最终的静态的 或者你可以使用懒惰单例初始化使得Account作为单例对象 并声明如下: 私人最终静态AccountSingleton acc_singleton = new AccountSingleton();

    +0

    谢谢你的回答,但我实际上建议不要将acct声明为volatile,而是acct.balance - 也就是类Account的字段余额。这会修改你的一些评论。我试图在这方面澄清这个问题。 – 2010-08-23 10:07:16

    1

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

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

    但是对于上面的代码,volatile不保证内存一致性,如果多个threds修改余额。 AtomicReferenceDouble类型服务于您的目的。

    相关SE问题:

    Difference between volatile and synchronized in Java