多线程环境下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,则它们不需要声明为 volatile
。 final
字段的特殊属性在 JLS 17.5.
中指定
如果long
常量不是final
,那么你需要做更深入的分析,以确定它们是否真的是常量,以及初始化的结果是否会被可见所有线程。
将(非final
)常量声明为volatile
可以实现这一点,但这不是一个好的(有效的)方法。深入分析(即仔细分析 happens-before 关系)可以揭示 volatile
是不必要的。例如,如果一个线程初始化常量,然后在所有其他使用它们的线程上调用 start()
,那么(我认为)你可以在没有 volatile
和其他同步的情况下逃脱。
但是...将常量声明为 final
是更可靠的方法2.
回复你更新问题中的伪代码:
伪代码版本不正确,即使在其他任何地方都没有更改。问题是是否保证所有线程都能看到 initial 值。问题在于内存模型不需要 long_val
由创建 ThreadSafeClass
实例的线程刷新。这意味着另一个线程在调用 calculate()
.
时可以看到默认初始值(零)
如果 local_long
在 synchronized
块中 初始化,伪代码将是正确的。
如果 long_val
是 final
或 volatile, then the
synchronized` 块将不需要(至少为此目的)。 (不同的原因...)
1 - 基本上,您需要确保在 "final" 生效之前没有其他线程使用常量字段。对于最终实例字段,这意味着在构造函数 returns 之前。对于最终的静态字段,这意味着在 class 初始化完成之前......当然要注意作为编译时常量的静态最终字段的处理方式不同。
2 - 我排除了使用反射更改 final
字段的极端情况。那是邪恶的,它使关于可见性的所有保证无效。只是不要这样做。
我有一个多线程 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,则它们不需要声明为 volatile
。 final
字段的特殊属性在 JLS 17.5.
如果long
常量不是final
,那么你需要做更深入的分析,以确定它们是否真的是常量,以及初始化的结果是否会被可见所有线程。
将(非final
)常量声明为volatile
可以实现这一点,但这不是一个好的(有效的)方法。深入分析(即仔细分析 happens-before 关系)可以揭示 volatile
是不必要的。例如,如果一个线程初始化常量,然后在所有其他使用它们的线程上调用 start()
,那么(我认为)你可以在没有 volatile
和其他同步的情况下逃脱。
但是...将常量声明为 final
是更可靠的方法2.
回复你更新问题中的伪代码:
伪代码版本不正确,即使在其他任何地方都没有更改。问题是是否保证所有线程都能看到 initial 值。问题在于内存模型不需要
long_val
由创建ThreadSafeClass
实例的线程刷新。这意味着另一个线程在调用calculate()
. 时可以看到默认初始值(零)
如果
local_long
在synchronized
块中 初始化,伪代码将是正确的。如果
long_val
是final
或volatile, then the
synchronized` 块将不需要(至少为此目的)。 (不同的原因...)
1 - 基本上,您需要确保在 "final" 生效之前没有其他线程使用常量字段。对于最终实例字段,这意味着在构造函数 returns 之前。对于最终的静态字段,这意味着在 class 初始化完成之前......当然要注意作为编译时常量的静态最终字段的处理方式不同。
2 - 我排除了使用反射更改 final
字段的极端情况。那是邪恶的,它使关于可见性的所有保证无效。只是不要这样做。