假设我有两个线程和一个对象。一个线程分配对象:
public void assign(MyObject o) {
myObject = o;
}
另一个线程使用该对象:
public void use() {
myObject.use();
}
变量 myObject 是否必须声明为 volatile?我试图了解何时使用 volatile 何时不使用,这让我感到困惑。第二个线程是否有可能在其本地内存缓存中保留对旧对象的引用?如果不是,为什么不呢?
非常感谢。
我试图了解何时使用 volatile 何时不使用
你应该尽量避免使用它。改为使用AtomicReference(或在适当的情况下使用另一个原子类)。记忆效果相同,意图更清晰。
我强烈建议阅读优秀的Java Concurrency in Practice以获得更好的理解。
抛开复杂的技术细节,你可以看到volatileless 或 more 作为variablessynchronized的修饰符。当您想同步对方法或块的访问时,您通常希望使用如下修饰符:synchronized
public synchronized void doSomething() {}
如果您想“同步”对变量的访问,那么您想使用volatile修饰符:
private volatile SomeObject variable;
在幕后他们做了不同的事情,但效果是一样的:下一个访问线程可以立即看到更改。
在您的具体情况下,我认为volatile修饰符没有任何价值。volatile不以任何方式保证分配对象的线程将在使用该对象的线程之前运行。反过来也一样好。您可能只想先在use()方法中执行 nullcheck。
更新:另见这篇文章:
对变量的访问就像它被包含在一个同步块中一样,在其自身上同步。我们在第二点中说“表现得好像”,因为至少对程序员(并且可能在大多数 JVM 实现中)没有实际涉及的锁定对象。
声明一个 volatile Java 变量意味着:
volatile 的典型和最常见的用法是:
public class StoppableThread extends Thread {
private volatile boolean stop = false;
public void run() {
while (!stop) {
// do work
}
}
public void stopWork() {
stop = true;
}
}
在这种情况下,您可以使用 volatile。您将需要围绕对变量的访问或某种类似机制(如 AtomicReference)的 volatile 同步,以保证在分配线程上所做的更改实际上对读取线程是可见的。
我花了很多时间试图理解volatile关键字。我认为@aleroot给出了世界上最好和最简单的例子。
这反过来又是我对假人的解释(像我一样:-)):
场景 1:假设stop未声明为 volatile,那么给定线程会执行并“认为”以下内容:
stopWork()被称为:我必须设置stop为true场景2:现在让stop声明为 volatile:
stopWork()被称为:我必须设置stop为truevolatile。我要占用CPU的时间长一点...没有同步,只是一个简单的想法......
为什么不声明所有变量volatile以防万一?由于 Scenario2/Step3。这有点低效,但仍然比常规同步好。
这里有一些令人困惑的评论:澄清一下,假设两个不同的线程调用assign()和use().
在没有volatile,或其他发生前的关系(例如,公共锁上的同步)的情况下,任何写入myObjectinassign()都不能保证被线程调用看到use()- 不是立即,不是及时,甚至永远不会.
是的,volatile这是纠正这个问题的一种方法(假设这是不正确的行为——在某些看似合理的情况下你并不关心这个!)。
您完全正确的是,“使用”线程可以看到 的任何“缓存”值myObject,包括在构造时分配的值和任何中间值(同样在没有其他发生之前的点的情况下)。