Java,会不稳定也能保证可见性?

时间:2012-11-27 20:13:00

标签: java concurrency

public class Stuff {
  private final Timer timer = new Timer(true);
  public static final int DEFAULT_TIMEOUT = 1500;
  private volatile int timeout = DEFAULT_TIMEOUT;


  public void doStuff(OtherStuff) {
     ...
     timer.schedule(timeout, ...);
  }

  public void setTimeout(int timeout) {
     this.timeout = timeout;
  }

  public int getTimeout() {
    return timeout;
  }

}

此类的实例仅从1个线程访问,但可以从另一个类更改的超时变量除外。在我的例子中是一个JMX bean,这意味着可以在运行时从管理界面改变超时。

doStuff()可以运行100次/秒,而setTimeout()可以每周运行一次 - 因此执行setTimeout()的人和执行doWork()的人之间的顺序并不重要。

对于这种情况,timeout是否足够挥发?内存模型是否可以保证将一个线程设置为doStuff()方法可见?

另一种似乎安全的替代方案就是:

public class Stuff {
  private final Timer timer = new Timer(true);
  public static final int DEFAULT_TIMEOUT = 1500;
  private int timeout = DEFAULT_TIMEOUT;
  public void doStuff(OtherStuff) {
     ...
     timer.schedule(getTimeout(), ...);
  }

  public void synchronized setTimeout(int timeout) {
     this.timeout = timeout;
  }
   public int synchronized getTimeout() {
    return timeout;
  }
}

这两种方法中哪一种更受青睐?

2 个答案:

答案 0 :(得分:7)

从可见性的角度来看,两种方法都是等价的。在对同一个volatile变量进行写操作之后发生的对volatile的任何读取都可以保证看到写入。

因此,如果一个线程写入timeout = newValue;,则随后调用timer.schedule(timeout)的任何其他线程都会看到newValue

此保证在JLS 17.4.5

中指定
  

对易失性字段(第8.3.1.4节)的写入发生在每次后续读取该字段之前。

我会简单地使用volatile,因为它提供的保证就足够了,它清楚地显示了你的意图。

答案 1 :(得分:0)

尽管这个例子的方法都是等价的,但我认为(因为问题的普遍性)值得讨论:

volatile变量的可见性效果超出了volatile变量本身的值。如果问题是“将挥发性还能保证同一个易变量的可见性吗?”。答案是肯定的。

但是无法保证在写入此变量之前使用的其他变量(如果有)的可见性。

来自Full volatile Visibility Guarantee

示例:

  

当线程A写入volatile变量并随后写入线程B.   读取相同的变量,即 ALL 变量的值   在写入volatile变量之前,A可见   到B AFTER 读取易变量。