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

时间:2015-12-19 03:53:02

标签: java multithreading volatile

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

建议制作这些长常量volatile吗?初始化后,这些常量不会被更改。我没有看到任何不正确的程序行为,只是想澄清一下。

这是伪代码,

public class ThreadSafeClass {
private long long_val = 100;

public int calculate(){

long local_long=long_val;

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

2 个答案:

答案 0 :(得分:5)

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

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

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

但是......将常量声明为final是更强大的方法 2

更新更新后的问题中的伪代码:

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

  2. 如果local_long已在 synchronized块中初始化,则伪代码将是正确的。

  3. 如果long_valfinalvolatile, then the已同步,则不需要阻止(至少为此目的)。 (出于不同的原因......)

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

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

答案 1 :(得分:1)

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

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