多线程环境下long类型实例常量是否必须声明volatile?

Is it mandatory to declare volatile for long type instance constants in multithreaded environments?

我有一个多线程 Java 程序,其中有几个长类型常量正在被多线程调用的函数读取。这些读取/赋值操作在 synchronized 块之外,并且这些常量在与被调用函数相同的 class / synchronized 块中声明。

是否建议使用这些长常量volatile?一旦初始化,这些常量就不会改变。我没有看到任何不正确的程序行为,但只是想澄清一下。

这是伪代码,

public class ThreadSafeClass {
private long long_val = 100;

public int calculate(){

long local_long=long_val;

synchronized(this){
  //use local_long
}}}

如果某物是常量,则它的值无法更改。

我看到 volatile 关键字的方式基本上是缓存警察。这将确保如果变量的值发生更改,则更改会反映在访问该变量的所有线程上。

但是由于常量永远不会改变,所以它永远不会有任何用处,因为您不能 运行 挂起或由于缓存而导致的任何事情。由于常量通常标记为 final,因此没有必要。

这是我所说的挂起示例:

static boolean done = false;
public boolean isDone() 
{
    return done;
}

在另一个线程中...

// do something, wait until this other thing is done...

while (! isDone()) 
{
    // Even if the thing becomes done, this is infinite: the value has been cached.
}

// when something else is done, then do the next thing.

还有一点,如果常量已经是 final,那么将其标记为 volatile 也会导致编译错误。干杯:)

如果 long 常量声明为 final 并且它们已安全发布1,则它们不需要声明为 volatilefinal 字段的特殊属性在 JLS 17.5.

中指定

如果long常量不是final,那么你需要做更深入的分析,以确定它们是否真的是常量,以及初始化的结果是否会被可见所有线程。

将(非final)常量声明为volatile可以实现这一点,但这不是一个好的(有效的)方法。深入分析(即仔细分析 happens-before 关系)可以揭示 volatile 是不必要的。例如,如果一个线程初始化常量,然后在所有其他使用它们的线程上调用 start(),那么(我认为)你可以在没有 volatile 和其他同步的情况下逃脱。

但是...将常量声明为 final 是更可靠的方法2.


回复你更新问题中的伪代码:

  1. 伪代码版本不正确,即使在其他任何地方都没有更改。问题是是否保证所有线程都能看到 initial 值。问题在于内存模型不需要 long_val 由创建 ThreadSafeClass 实例的线程刷新。这意味着另一个线程在调用 calculate().

  2. 时可以看到默认初始值(零)
  3. 如果 local_longsynchronized 块中 初始化,伪代码将是正确的。

  4. 如果 long_valfinalvolatile, then thesynchronized` 块将不需要(至少为此目的)。 (不同的原因...)


1 - 基本上,您需要确保在 "final" 生效之前没有其他线程使用常量字段。对于最终实例字段,这意味着在构造函数 returns 之前。对于最终的静态字段,这意味着在 class 初始化完成之前......当然要注意作为编译时常量的静态最终字段的处理方式不同。

2 - 我排除了使用反射更改 final 字段的极端情况。那是邪恶的,它使关于可见性的所有保证无效。只是不要这样做。