Java 字段读取同步和易失性同步之间的差异

Java 字段读取同步和易失性同步之间的差异,java,concurrency,synchronization,volatile,synchronized,Java,Concurrency,Synchronization,Volatile,Synchronized,在nice中,一个示例优化为以下行: double getBalance() { Account acct = verify(name, password); synchronized(acct) { return acct.balance; } } 如果我理解正确,同步的要点是确保该线程读取的acct.balance的值是最新的,并且对acct.balance中对象字段的任何挂起写入也会写入主存 这个例子让我思考了一下:仅仅将acct.balance(即类帐户的字段余额)声明

在nice中,一个示例优化为以下行:

double getBalance() {
    Account acct = verify(name, password);
    synchronized(acct) { return acct.balance; }
}
如果我理解正确,同步的要点是确保该线程读取的acct.balance的值是最新的,并且对acct.balance中对象字段的任何挂起写入也会写入主存


这个例子让我思考了一下:仅仅将acct.balance(即类帐户的字段余额)声明为
volatile
,不是更有效吗?它应该更高效,在访问acct.balance时保存所有的
同步
,并且不会锁定整个
acct
对象。我遗漏了什么吗?

你是对的。volatile提供了可见性保证。synchronized提供了受保护代码段的可见性保证和序列化。对于非常简单的情况,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”答案很可能是“是”,但花时间/精力来确定它,以及出错的危险,是不值得的(性能不高)


另一方面,编写良好的Account类将在内部处理所有同步逻辑,因此调用者不必担心它。

将Account声明为volatile会受到以下问题和限制

1.“由于其他线程看不到局部变量,声明局部变量volatile是徒劳的”此外,如果您试图在方法中声明volatile变量,在某些情况下会出现编译器错误

双重getBalance(){ volatile acct=验证(名称、密码);//不正确。。 }

  • 将Account声明为volatile会警告编译器每次取新的帐户,而不是将它们缓存在寄存器中。这也会禁止某些假设没有其他线程会意外更改值的优化

  • 如果需要同步以协调来自不同线程的变量更改, volatile不保证原子访问,因为访问volatile变量永远不会持有锁,所以它不适合我们希望将读更新写作为原子操作的情况。除非您确定acct=verify(名称、密码);是单原子操作,不能保证例外结果

  • 如果变量acct是一个对象引用,那么它很可能是空的。尝试对空对象进行同步将使用synchronized抛出NullPointerException。 (因为您实际上是在引用上同步,而不是在实际对象上同步) 哪里不抱怨

  • 相反,您可以像这里一样将布尔变量声明为volatile

    私有易失布尔someAccountflag

    公共资产负债表(){ 会计科目; 而(!someAccountflag){ acct=验证(名称、密码); } }

  • 注意:不能将someAccountflag声明为已同步,如 您无法使用synchronized在原语上进行同步,synchronized仅适用于对象变量,其中as原语或对象变量可能声明为volatile

    6.类的最终静态字段不需要是易变的,JVM会处理这个问题。因此,如果someAccountflag是最终静态的,那么它甚至不需要声明为volatile 或者,您可以使用惰性单例初始化将Account作为单例对象 并声明如下:
    私有最终静态AccountSingleton acc_singleton=新AccountSingleton()

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

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

    但对于上述代码,
    volatile
    不能保证在多个thred修改平衡时内存的一致性。使用
    Double
    类型符合您的目的

    相关问题:


    你是对的,但这篇文章实际上是关于完全不同的东西——减少锁定范围。谢谢你的回答,但我实际上建议不要将acct声明为volatile,而是acct.balance——也就是类Account的字段余额。这会修改你的一些评论。我试图在这方面稍微澄清一下这个问题。