在Java中什么时候不应该使用volatile变量

3

我了解volatile变量只保证可见性,不应用于原子/复合操作。然而,我认为我在某个地方读到过,如果只有一个线程在进行值的更新(在单个操作中),它也可以使用。这是正确的吗?

所以,如果两个线程正在更新volatile变量,比如布尔标志,这是线程安全的吗?


这真的取决于你如何使用它。有一些使用情况是正确的,有一些则不是。多线程是一个复杂而微妙的问题,因此像“如果你有多个写线程,就永远不应该使用volatile”这样的概括很少能成立。相反,你必须知道volatile给你什么,同样重要的是,它给不了你什么,然后再将其小心地应用到手头的情况中。这是棘手的事情,所以(a)你应该找一个好的合作伙伴来与你一起进行代码审查,(b)你应该考虑使用其中一个更高级的结构,比如BlockingQueue等。 - yshavit
总的来说,与其他语言(特别是C和C++)不同,在Java中,“volatile”实际上提供了一些同步保证。 - hyde
@yshavit 我已经为您缩小了情境范围:如果两个线程正在更新一个易失变量,比如布尔标志(在单个操作中将其设置为true或false),并且没有任何其他形式的同步,那么这是线程安全的吗? - Shivam Sinha
再次强调,这取决于具体情况。它们是否都将其更新为相同的值,基本上像一个锁存器?如果不是,如果读取线程错过了更新,那么对于您的应用程序是否可以接受?也就是说,如果布尔值从 true 变为 false 再变为 true,而我的读取线程只看到它为 true(它从未看到 false),那么这样可以吗?对于某些用途来说,可以,但对于其他用途则不行。 - yshavit
1个回答

5
我理解volatile变量只保证可见性,不应该用于原子/复合操作。
实际上,所有AtomicXxxx类都使用volatile,但它们以安全的方式使用,这是重要的区别。并非所有操作都是安全的。
我记得曾在某个地方读到,仅当一个线程更新值时才能使用它。这是正确的吗?
这是一种解决方法。如果只有一个写入者,对于该字段使用volatile是可以的。
注意:普遍存在这样一个误解,即volatile对使用它的任何操作都具有线程安全性。例如,
volatile int[] a = { 0 };

a[0]++; // not thread safe even if you have only 1 writer.

这是因为对于a的写操作以及仅针对a的操作是易失性的。而指向a的任何内容都不是易失性的。这与final没有区别。
final int[] a = { 0 };
a = null; // cannot do this
a[0] = 1; // compiles fine, is not final.

我为您梳理了情境:如果两个线程通过单一操作更新一个名为 boolean flag 的易失性变量(比如将其设置为 true 或 false),并且没有其他形式的同步机制,这是否线程安全?
只有当您只有一个写入者或两个写入者都设置为相同的值时,它才是安全的。例如:
flag = true; // ok, provided no thread sets it to false.
flag = !flag; // not ok.

3
「a」指向的任何东西都是「volatile」,这句话不应该改成「不是volatile」。 - Roman
1
@Nier 只有单个写入者时才是线程安全的,就像 x++;。AtomicInteger 使用 compareAndSwap 操作。 - Peter Lawrey
1
关于最后一点,如果可以忽略更新,那么也是安全的。举个我想到的例子,也许我有一些线程定期检查一个volatile boolean isPaused。我的线程检查并看到它没有暂停,所以它做了一些工作;稍后,它再次检查并看到它仍然没有暂停。在这两个操作之间,如果有人暂停然后再次取消暂停,它可能并不在意。 - yshavit
1
@yshavit 如果你有性能指标并且打印出缓存命中率,比如打印出33%,那么即使丢失了1000个增量中的1个,也没有关系,但是通过不将其设置为“volatile”来获得更好的性能可能是值得的。 - Peter Lawrey
@ShivamSinha 在Java中访问CPU缓存命中率很棘手,我指的是软件缓存,比如数据库中的内存缓存。 - Peter Lawrey
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接