java - 字段读取同步和volatile的区别

标签 java concurrency synchronization volatile synchronized

在一个不错的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/

相关文章:

java - jdk7 : sun. font.fontManager 替换/如何从 fontname 获取文件名信息

java - ADF Faces中是否有类似ExtJS的MessageBox

c - 具有多个关键部分以同步两个共享队列

Java同步-等待来自服务器的数据

java - 什么类替换了 javax webservice API (javax.ws.rs-api) 中封存的 'ClientFactory'?

java - 检查forEach lambda循环Java 8中List的范围

java - 具有非最终字段的不可变对象(immutable对象)如何成为线程不安全的?

java - 与 ConcurrentSkipListSet 一起正常工作的即时比较器

concurrency - 事件溯源 : concurrently creating conflicting events

macos - 使用化妆同步 macos 设置