我有一个多线程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
}}}
答案 0 :(得分:5)
如果long
常量声明为final
并且它们已安全发布 1 ,那么它们不需要声明为volatile
。 final
字段的特殊属性在JLS 17.5中指定。
如果long
常量不是final
,那么您需要进行更深入的分析以确定它们是否真的是常量,以及初始化的结果是否对所有线程都可见。
将(非final
)常量声明为volatile
可以实现这一点,但这不是一种好的(有效的)方法。深入分析(即仔细分析之前发生的关系)可以揭示volatile
是不必要的。例如,如果一个线程初始化了常量,然后THEN在使用它们的所有其他线程上调用start()
,那么(我认为)你可以没有volatile
而没有其他同步。< / p>
但是......将常量声明为final
是更强大的方法 2 。
更新更新后的问题中的伪代码:
伪代码版本不正确,即使假设其他地方没有更改。问题是是否所有线程都能保证看到初始值。问题是内存模型不需要由创建long_val
实例的线程刷新ThreadSafeClass
。这意味着另一个线程在调用calculate()
时可以看到默认的初始值(零)。
如果local_long
已在 synchronized
块中初始化,则伪代码将是正确的。
如果long_val
为final
或volatile, then the
已同步,则不需要阻止(至少为此目的)。 (出于不同的原因......)
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
也会出现编译错误。干杯:)