使用 volatile long 有啥意义吗?
Posted
技术标签:
【中文标题】使用 volatile long 有啥意义吗?【英文标题】:Is there any point in using a volatile long?使用 volatile long 有什么意义吗? 【发布时间】:2011-03-03 13:34:03 【问题描述】:我偶尔会使用volatile
实例变量,因为我有两个线程读取/写入它并且不希望获取锁的开销(或潜在的死锁风险);例如,一个计时器线程定期更新一个 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 位读取将是原子的,曾经使用 volatile long 有什么意义吗? (即 64 位)。
警告:请不要回复说使用volatile
而不是synchronized
是预优化的情况;我很清楚如何/何时使用synchronized
,但在某些情况下volatile
更可取。例如,在定义用于单线程应用程序的 Spring bean 时,我倾向于使用 volatile
实例变量,因为不能保证 Spring 上下文会在主线程中初始化每个 bean 的属性。
【问题讨论】:
注意,++
不是原子操作。我建议改用java.util.concurrent.atomic.AtomicInteger
。
谢谢汤姆 - 这只是一个简单的例子。
顺便说一句,你不需要在 Spring 中使用volatile
。 Spring 在初始化您的类时使用锁,并且发生前的关系将为您处理排序
【参考方案1】:
这可以通过例子来证明
不断切换两个字段,一个标记为易失性,一个不在所有位设置和所有位清除之间 在另一个线程上读取字段值 看到 foo 字段(不受 volatile 保护)可以在不一致的状态下读取,这不会发生在使用 volatile 保护的 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 上,我在几分钟内无法得到一个错误。
【讨论】:
您还可以添加它出现在该输出中的原因,这是因为它在第一步中分两步写入第一个 32 位,在第二步中写入另一个 32 位。【参考方案2】:“volatile”有多种用途:
保证原子写入双/长 保证当线程 A 看到线程 B 对 volatile 变量所做的更改时,线程 A 还可以看到线程 B 在更改为 volatile 变量之前所做的所有其他更改(考虑在设置细胞本身)。 基于只有一个线程可以更改变量的假设阻止编译器优化(想想紧密循环while (l != 0)
。
还有吗?
【讨论】:
【参考方案3】:不确定我是否正确理解了您的问题,但JLS 8.3.1.4. volatile Fields 声明:
一个字段可能被声明为 volatile,在这种情况下,Java 内存模型确保所有线程都能看到变量的一致值 (§17.4)。
也许更重要的是,JLS 17.7 Non-atomic Treatment of double and long:
17.7双长的非原子处理 [...] 出于 Java 编程语言内存模型的目的,对非易失性 long 或 double 值的单次写入被视为两次单独的写入:每个 32 位一半。这可能会导致线程从一次写入中看到 64 位值的前 32 位,而从另一次写入中看到后 32 位。 volatile long 和 double 值的写入和读取始终是原子的。 引用的写入和读取始终是原子的,无论它们是作为 32 位还是 64 位值实现的。
也就是说,“整个”变量受到 volatile 修饰符的保护,而不仅仅是两个部分。这诱使我声称对long
s 使用volatile 比对int
s 使用更重要,因为甚至读取对于非不稳定的多头/双打。
【讨论】:
float
value 呢,也是 16 字节?
我无法想象是什么导致了线程从一次写入中看到 64 位值的前 32 位,而从另一次写入中看到后 32 位的情况。你能告诉我吗?
一个线程将一个 64 位的值写入一个变量。由于这不是所有硬件上的原子操作,因此实际上可以分为两个写操作。另一个线程可能会出现并尝试读取这两个写操作之间的值。然后它将看到一个基于旧值的 32 位和新值的 32 位的值。请参阅下面亚当的回答。以上是关于使用 volatile long 有啥意义吗?的主要内容,如果未能解决你的问题,请参考以下文章
ANSI C 标准定义的基本数据类型取值范围数字有啥特殊意义