3
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对于这种情况,使volatile 足够吗?内存模型会保证从一个线程设置此方法对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;
  }
}

这两种方法中的哪一种更受欢迎?

4

2 回答 2

7

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

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

此保证在JLS 17.4.5中指定:

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

我会简单地使用 volatile ,因为它提供的保证已经足够,并且清楚地表明了您的意图。

于 2012-11-27T20:15:43.230 回答
0

尽管该示例的两种方法都是等效的,但我认为(由于问题的普遍性)值得讨论一下:

volatile 变量的可见性影响超出了 volatile 变量本身的值。如果问题是“ volatile 是否也能保证同一个 volatile 变量的可见性?”。答案是肯定的。

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

来自完全不稳定的可见性保证

例子:

当线程 A 写入 volatile 变量并且随后线程 B 读取相同的变量时,在写入 volatile 变量之前对 A 可见的所有变量的值在读取 volatile 变量之后对 B 可见。

于 2018-05-19T08:25:18.623 回答