Java中的volatile标识符

5

我不理解我读到的那几个语句:

因为访问一个volatile变量从不持有锁,所以它不适用于我们想要将读取-更新-写入作为原子操作的情况(除非我们准备“错过更新”);

这是什么意思,我无法进行读取-更新-写入?

什么时候需要使用volatile而不是简单的布尔值。在C#中,我记得可以使用简单的静态布尔值来控制线程的启动和停止,但在Java中,我需要使用标识符“volatile”:

> public class StoppableTask extends Thread {
  private volatile boolean pleaseStop;

  public void run() {
    while (!pleaseStop) {
      // do some stuff...
    }
  }

  public void tellMeToStop() {
    pleaseStop = true;
  }
}

1
如果您不介意错过更新并运行一两次“太多”的迭代,那么您就不需要使用volatile。(您不会再将其设置为false了,对吧?)要在Java中编写更容易/更安全/更健康的并发代码,请查看java.util.concurrent。它具有线程管理类,还有一个AtomicBoolean。 - Thilo
如果您查看算法,它应该将其设置为true和false,并防止循环无限。 - Dmitry Makovetskiyd
4
@Thilo: 不,你需要使用volatile!没有它,不能保证线程会看到标志被设置为true。实际上,某些编译器优化甚至可能从循环中删除条件并使其成为一个无限循环。 - Philipp Wendler
3
如果在上面的例子中不使用volatile,你可能会造成无限循环。 - Petar Minchev
1
我明白了。更加理由是停止搞低级的东西,使用java.util.concurrent。 - Thilo
2个回答

11

这句话是关于这个例子的:

public class CounterClass {
   private volatile int counter;

   public int increment() {
       return counter++;
   }
}
countervolatile修饰符,但如果两个线程访问increment()方法,不能保证会返回两个不同的整数。因为counter++操作不是原子操作,它是获取、增加、返回三步操作。两个线程同时调用increment()是有可能的,第一个线程在获取阶段,而第二个线程也在获取阶段,然后第二个线程在第一个线程进入增加阶段之前就进入了增加阶段。
总结: volatile并不保证原子性或其他任何线程安全和原子性方面的保障。它只是保证当你访问变量时,你不会得到旧的缓存值。

2
什么时候我会想要使用volatile?
顺便提一下,Brian Goetz在《管理易变性》中列出了五种使用volatile的模式:
1.状态标志(“规范”) 2.一次安全发布 3.独立观察(“前一个的扩展”) 4.易变豆模式 5.廉价读写锁技巧(“如果读取远远超过修改”)
顺便提一下,这个图片帮助我理解了易失性变量的内存保证:
https://farm4.static.flickr.com/3073/3035268779_f8a9dce89d.jpg
在上面的代码中,ready 是易失性变量。请注意,无论有什么保证,这些仅适用于赋值,而不适用于读取-更新-写入操作。

  • 上面的图片摘自Jeremy Manson的博客文章:Java中易失性变量的含义

    "第一个线程写入 ready,这将是通信的发送方。第二个线程从 ready 中读取并看到第一个线程写入它的值。因此,它成为接收方。由于发生了这种通信,在第一个线程写入 ready 之前看到的所有内存内容必须在第二个线程读取 ready 的值 true 后对第二个线程可见。"


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