字段读取同步和易失性之间的区别

发布于 2024-09-06 17:49:58 字数 576 浏览 4 评论 0原文

在一篇不错的文章中提供了一些并发技巧< /a>,一个示例被优化为以下几行:

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

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

这个例子让我思考了一下:将acct.balance(即类Account的字段balance)声明为易失性不是更高效吗?它应该更高效,节省您访问 acct.balance 时的所有synchronize,并且不会锁定整个 acct 对象。我错过了什么吗?

In a nice article with some concurrency tips, an example was optimized to the following lines:

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

If I understand that correctly, the point of the synchronization is to ensure that the value of acct.balance that are read by this thread is current and that any pending writes to the fields of the object in acct.balance are also written to main memory.

The example made me think a little: wouldn't it be more efficient to just declare acct.balance (i.e. the field balance of class Account) as volatile? It should be more efficient, save you all the synchronize on accesses to acct.balance and would not lock the whole acct object. Am I missing something?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

深空失忆 2024-09-13 17:49:58

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

如果您假设帐户有一种调整其余额的方法,那么波动性就不够好

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

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

You are correct. volatile provides a visibility guarantee. synchronized provides both a visibility guarantee AND serialisation of protected code sections. For VERY simple situations volatile is enough, however it is easy to get into trouble using volatile instead of synchronisation.

If you were to assume that Account has a way of adjusting its balance then volatile is not good enough

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

Then we have a problem if balance is volatile with no other synchronization. If two threads were to try and call add() together you could have a "missed" update where the following happens

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!)

Obviously this is wrong because both threads read the current value and updated independently and then wrote it back (read, compute, write). volatile does not help here so you would need synchronized to ensure one thread completed the entire update before the other thread began.

I general find that if when writing some code I think "can I use volatile instead of synchronized" the answer might well be "yes" but the time/effort of figuring it out for sure and the danger of getting it wrong is not worth the benefit (minor performance).

As an aside a well written Account class would handle all the synch logic internally so callers don't have to worry about it.

旧情别恋 2024-09-13 17:49:58

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

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

双 getBalance() {
易失性帐户 acct = 验证(名称,密码); //不正确..

  1. Account声明为易失性会警告编译器每次都重新获取它们,而不是将它们缓存在寄存器中。这也会抑制某些优化,即假设没有其他线程会意外更改值。

  2. 如果您需要同步来协调不同线程对变量的更改,
    易失性不能保证原子访问,因为访问易失性变量永远不会持有锁,它不适合我们希望将读-更新-写作为原子操作的情况。除非你确定 acct = verify(name,password);是单个原子操作,不能保证异常结果

  3. 如果变量 acct 是一个对象引用,那么它很可能为 null 。尝试在 null 对象上同步将使用synchronized 抛出 NullPointerException
    (因为您实际上是在引用上同步,而不是在实际对象上同步)
    挥发性不会抱怨

  4. 相反,你可以像这里一样将布尔变量声明为挥发性

    私有易失性布尔值someAccountflag;

    公共无效getBalance(){
    账户账户;
    while (!someAccountflag) {
    acct = 验证(姓名,密码);
    }
    相反

注意,您不能将 someAccountflag 声明为同步的,因为
您无法使用synchronized在基元上进行同步,synchronized仅适用于对象变量,因为基元或对象变量可以声明为易失性

6.类的最终静态字段不需要是易失性,JVM 解决了这个问题。因此,如果 someAccountflag 是最终静态变量,则甚至不需要将其声明为 volatile
或者您可以使用惰性单例初始化使 Account 作为单例对象
并声明如下:
私有最终静态AccountSingleton acc_singleton = new AccountSingleton();

Declaring Account as volatile is subjected to following issues and restrictions

1."Since other threads cannot see local variables, declaring local variables volatile is futile." Moreover If you try to declare a volatile variable in a method, you'll get a compiler error in some cases.

double getBalance() {
volatile Account acct = verify(name, password); //Incorrect ..
}

  1. Declaring Account as volatile warns the compiler to fetch them fresh each time, rather than caching them in registers. This also inhibits certain optimizations that assume no other thread will change the values unexpectedly.

  2. If you need synchronized to co-ordinate changes to variables from different threads,
    volatile does not guarantee you atomic access,because accessing a volatile variable never holds a lock, it is not suitable for cases where we want to read-update-write as an atomic operation. Unless you are sure that acct = verify(name, password); is single atomic operation, you cannot guarantee excepted results

  3. If variable acct is an object reference, then chances are it may be null .Attempting to synchronize on a null object will throw a NullPointerException using synchronized.
    (because you're effectively synchronizing on the reference, not the actual object)
    Where as volatile does not complain

  4. Instead you could declare a boolean variable as volatile like here

    private volatile boolean someAccountflag;

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

Note you cannot declare someAccountflag as synchronized, as
you can't synchronize on a primitive with synchronized, synchronized only works with object variables, where as primitive or object variable may be declared volatile

6.Class final static fields doesn't need to be volatile, JVM takes care of this problem. So the someAccountflag need not be even declared volatile if it is a final static
or you could use lazy singleton initialization making Account as a singleton object
and declare it as follows:
private final static AccountSingleton acc_singleton = new AccountSingleton ();

一个人的旅程 2024-09-13 17:49:58

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

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

但对于上面的代码,如果多个线程修改余额,易失性并不能保证内存一致性。 AtomicReferenceDouble 类型可以满足您的目的。

相关的SE问题:

Java中的volatile和synchronized之间的区别

If multiple threads are modifying and accessing the data, synchronized guarantees data consistency among multiple threads .

if single thread is modifying the data and multiple threads try to read the latest value of data, use volatile construct.

But for above code, volatile does not guaranty memory consistency if multiple threds modify balance. AtomicReference with Double type serves your purpose.

Related SE question:

Difference between volatile and synchronized in Java

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文