使用 volatile long 有什么意义吗?

发布于 2024-09-05 18:38:21 字数 801 浏览 3 评论 0原文

当我有两个线程读取/写入它并且不希望取出锁的开销(或潜在的死锁风险)时,我偶尔会使用 易失性实例变量;例如,一个计时器线程定期更新一个 int ID,该 ID 在某个类上作为 getter 公开:

public class MyClass {
  private volatile int id;

  public MyClass() {
    ScheduledExecutorService execService = Executors.newScheduledThreadPool(1);
    execService.scheduleAtFixedRate(new Runnable() {
      public void run() {
        ++id;
      }
    }, 0L, 30L, TimeUnit.SECONDS);
  }

  public int getId() {
    return id;
  }
}

我的问题:鉴于 JLS 仅保证 32 位读取将是原子的,是否有任何一点永远使用易失性长? (即 64 位)。

警告:请不要回复说使用 volatile 而不是 synchronized 是预优化的情况;我很清楚如何/何时使用synchronized,但在某些情况下volatile更可取。例如,当定义在单线程应用程序中使用的 Spring bean 时,我倾向于使用易失性实例变量,因为不能保证 Spring 上下文会在主线程中初始化每个 bean 的属性。

I occasionally use a volatile instance variable in cases where I have two threads reading from / writing to it and don't want the overhead (or potential deadlock risk) of taking out a lock; for example a timer thread periodically updating an int ID that is exposed as a getter on some class:

public class MyClass {
  private volatile int id;

  public MyClass() {
    ScheduledExecutorService execService = Executors.newScheduledThreadPool(1);
    execService.scheduleAtFixedRate(new Runnable() {
      public void run() {
        ++id;
      }
    }, 0L, 30L, TimeUnit.SECONDS);
  }

  public int getId() {
    return id;
  }
}

My question: Given that the JLS only guarantees that 32-bit reads will be atomic is there any point in ever using a volatile long? (i.e. 64-bit).

Caveat: Please do not reply saying that using volatile over synchronized is a case of pre-optimisation; I am well aware of how / when to use synchronized but there are cases where volatile is preferable. For example, when defining a Spring bean for use in a single-threaded application I tend to favour volatile instance variables, as there is no guarantee that the Spring context will initialise each bean's properties in the main thread.

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

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

发布评论

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

评论(3

远昼 2024-09-12 18:38:21

不确定我是否正确理解您的问题,但是 JLS 8.3.1.4。易失性字段指出:

字段可以声明为易失性的,在这种情况下,Java 内存模型确保所有线程看到变量的一致值 (§17.4)。

也许更重要的是, JLS 17.7 非-double和long的原子处理

17.7 double 和 long 的非原子处理
[...]
出于 Java 编程语言内存模型的目的,对非易失性 long 或 double 值的单次写入被视为两次单独的写入:每个 32 位一半写入一次。这可能会导致线程从一次写入中看到 64 位值的前 32 位,而从另一次写入中看到第二个 32 位。 易失性长整型和双精度值的写入和读取始终是原子的。引用的写入和读取始终是原子的,无论它们是实现为 32 位值还是 64 位值。

也就是说,“整个”变量受到 volatile 修饰符的保护,而不仅仅是两个部分。这让我忍不住想说,在 long 中使用 volatile 比在 int 中使用更重要,因为甚至都不是对于非易失性长整型/双精度,读取是原子的。

Not sure if I understand your question correctly, but the JLS 8.3.1.4. volatile Fields states:

A field may be declared volatile, in which case the Java memory model ensures that all threads see a consistent value for the variable (§17.4).

and, perhaps more importantly, JLS 17.7 Non-atomic Treatment of double and long :

17.7 Non-atomic Treatment of double and long
[...]
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64 bit value from one write, and the second 32 bits from another write. Writes and reads of volatile long and double values are always atomic. Writes to and reads of references are always atomic, regardless of whether they are implemented as 32 or 64 bit values.

That is, the "entire" variable is protected by the volatile modifier, not just the two parts. This tempts me to claim that it's even more important to use volatile for longs than it is for ints since not even a read is atomic for non-volatile longs/doubles.

流年已逝 2024-09-12 18:38:21

这可以通过示例

  • 不断切换两个字段来证明,一个标记为易失性,一个不在所有位设置和所有位清除之间切换,
  • 在另一个线程上读取字段值,
  • 看看 foo 字段(不受易失性保护)可以在不一致的状态下读取 保护的 bar 字段上。

,这种情况永远不会发生在受易失性代码

public class VolatileTest {
    private long foo;
    private volatile long bar;
    private static final long A = 0xffffffffffffffffl;
    private static final long B = 0;
    private int clock;
    public VolatileTest() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    foo = clock % 2 == 0 ? A : B;
                    bar = clock % 2 == 0 ? A : B;
                    clock++;
                }
            }

        }).start();
        while (true) {
            long fooRead = foo;
            if (fooRead != A && fooRead != B) {
                System.err.println("foo incomplete write " + Long.toHexString(fooRead));
            }
            long barRead = bar;
            if (barRead != A && barRead != B) {
                System.err.println("bar incomplete write " + Long.toHexString(barRead));
            }
        }
    }

    public static void main(String[] args) {
        new VolatileTest();
    }
}

输出

foo incomplete write ffffffff00000000
foo incomplete write ffffffff00000000
foo incomplete write ffffffff
foo incomplete write ffffffff00000000

请注意,这只发生在我在 32 位 VM 上运行时,在 64 位 VM 上我在几分钟内无法收到任何错误。

This can be demonstrated by example

  • constantly toggle two fields, one marked volatile and one not between all bits set and all bits clear
  • read the field values on another thread
  • see that the foo field (not protected with volatile) can be read in an inconsistent state, this never happens to the bar field protected with volatile

Code

public class VolatileTest {
    private long foo;
    private volatile long bar;
    private static final long A = 0xffffffffffffffffl;
    private static final long B = 0;
    private int clock;
    public VolatileTest() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    foo = clock % 2 == 0 ? A : B;
                    bar = clock % 2 == 0 ? A : B;
                    clock++;
                }
            }

        }).start();
        while (true) {
            long fooRead = foo;
            if (fooRead != A && fooRead != B) {
                System.err.println("foo incomplete write " + Long.toHexString(fooRead));
            }
            long barRead = bar;
            if (barRead != A && barRead != B) {
                System.err.println("bar incomplete write " + Long.toHexString(barRead));
            }
        }
    }

    public static void main(String[] args) {
        new VolatileTest();
    }
}

Output

foo incomplete write ffffffff00000000
foo incomplete write ffffffff00000000
foo incomplete write ffffffff
foo incomplete write ffffffff00000000

Note this only happens for me when running on a 32 bit VM, on 64 bit VM I couldn't get a single error in several minutes.

橙幽之幻 2024-09-12 18:38:21

“易失性”有多种用途:

  • 保证原子写入双/长
  • 保证当线程A看到线程B对易失性变量所做的更改时,线程A也可以看到线程B在对易失性变量进行更改之前所做的所有其他更改(想想在设置单元格本身后设置数组中使用的单元格数量)。
  • 基于只有一个线程可以更改变量的假设来阻止编译器优化(想想紧密循环 while (l != 0) {}

还有更多吗?

"volatile" serves multiple purposes:

  • guarantees atomic writes to double/long
  • guarantees that when a thread A sees change in volatile variable made by thread B, thread A can also see all other changes made by thread B before the change to volatile variable (think setting the number of used cells in array after setting the cells themselves).
  • prevents compiler optimization based on assumption that only one thread can change the variable (think tight loop while (l != 0) {}.

Is there more?

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