【问题标题】:Is it mandatory to declare volatile for long type instance constants in multithreaded environments?是否必须在多线程环境中为长类型实例常量声明 volatile?
【发布时间】:2015-12-19 03:53:01
【问题描述】:

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

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

这是伪代码,

public class ThreadSafeClass {
private long long_val = 100;

public int calculate(){

long local_long=long_val;

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

【问题讨论】:

  • 没有 volatile 常量这种东西,但这里也没有 long 类型的常量变量。有整数常量和非常量长变量。您在这里缺少的是“final”,这将使长变量保持不变,从而消除您的问题。
  • 请定义 you 所指的“常数”(在您的程序上下文中)。你如何定义你的常量?如果它没有使用 final 关键字,你可能做错了。
  • 感谢您的回复,我说不变是因为它在代码中的其他任何地方都没有改变,尽管原始开发人员没有使用final。我认为,将其设为final 将消除混淆,并且不需要volatile(使用变量的方式)
  • 那是正确的。

标签: java multithreading volatile


【解决方案1】:

如果long 常量被声明为final 并且它们被安全地发布1,那么它们不需要被声明为volatilefinal 字段的特殊属性在 JLS 17.5 中指定。

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

将(非final)常量声明为volatile 可以实现这一点,但这不是一个好的(有效)方法。深入分析(即仔细分析之前发生的关系)可能会发现volatile 是不必要的。例如,如果一个线程初始化了常量,然后在所有其他使用它们的线程上调用start(),那么(我认为)你可以不使用volatile,也不需要其他同步。

但是...将常量声明为final 是更稳健的方法2


在您更新的问题中重新添加伪代码:

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

  2. 如果local_long 被初始化synchronized 块内,则伪代码是正确的。

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


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

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

【讨论】:

  • 这里,一个长实例变量,初始化为一个固定值,并且在代码中的任何地方都没有改变(代码是由其他人编写的),没有标记为 final 在函数局部变量中被读取并且该局部变量正在该函数内的同步块中使用。我认为长时间的原子读取问题不应该成为这种情况的问题。
  • @Sabir_Khan 只是为了了解一些背景,程序是多线程的吗?如果不是,你真的不需要担心同步或类似的疯狂。
  • 我不是在谈论原子读取问题。我说的是一个更普遍的问题,即读取线程是否看到正确的初始值......当初始值由另一个线程设置时。所有原始类型都会出现同样的问题。
  • 感谢您提供有用的见解,根据我的回忆,我已将伪代码添加到问题中,因为实际代码位于办公室 PC 上,而我目前无法访问该代码。最初的开发者可能有其他意图,但我记得没有看到 long_val 在任何地方被更改。
  • 伪代码版本不正确,但如果local_longsynchronized块内初始化,那就是正确的。
【解决方案2】:

如果某个东西是常数,它的值就不能改变。

我看到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 也会导致编译错误。干杯:)

【讨论】:

  • 好的,程序是由其他开发人员编写的,我没有看到任何地方的值被改变,所以我称它为常量,但它没有标记为 final。如果这是它的行为方式,它可能应该被标记为 final。
  • @Sabir_Khan 好吧,如果它不被更改,那么 volatile 关键字的用途并不大。我将提供我在回答中谈到的这种挂起的代码示例。
猜你喜欢
  • 2014-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-17
  • 2012-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多