在一个不错的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
对象。我错过了什么吗?
最佳答案
你是对的。 volatile 提供可见性保证。 synchronized 提供可见性保证和 protected 代码段的序列化。对于非常简单的情况,volatile 就足够了,但是使用 volatile 而不是同步很容易遇到麻烦。
如果您假设 Account 有调整其余额的方法,那么 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 而不是同步”,那么答案很可能是"is",但是确定地弄清楚它的时间/努力以及弄错它的危险是不值得的好处(次要表现)。
顺便说一句,一个写得很好的账户类会在内部处理所有的同步逻辑,所以调用者不必担心它。
关于java - 字段读取同步和volatile的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3103204/