我有一个多线程Java程序,其中很少有长类型的常量,这些常量在多个线程调用的函数中被读取.这些读取/赋值操作在synchronized
块外部,并且这些常量在与调用/ synchronized
block的函数相同的类中声明.
这些长常数是否可取volatile
?初始化后,这些常量不会被更改.我没有看到任何不正确的程序行为,只是想澄清一下.
这是伪代码,
public class ThreadSafeClass { private long long_val = 100; public int calculate(){ long local_long=long_val; synchronized(this){ //use local_long }}}
Stephen C.. 5
如果long
常量声明为final
并且它们安全地发布为1,那么它们不需要声明为volatile
.final
字段的特殊属性在JLS 17.5中指定.
如果long
常量不是final
,则需要进行更深入的分析以确定它们是否真的是常量,以及初始化的结果是否对所有线程都可见.
声明(非final
)常量volatile
会实现这一点,但这不是一种好的(有效的)方法.深入分析(即仔细分析之前发生的关系)可能会发现这volatile
是不必要的.例如,如果一个线程初始化了常量,然后THEN调用start()
了所有使用它们的其他线程,那么(我认为)你可以逃避volatile
没有其他同步.
但是......宣布常数final
是更强大的方法2.
更新更新后的问题中的伪代码:
伪代码版本是不正确的,即使它在其他任何地方都没有改变.问题是是否所有线程都能保证看到初始值.问题是内存模型不需要long_val
由创建ThreadSafeClass
实例的线程刷新.这意味着另一个线程在调用时可以看到默认的初始值(零)calculate()
.
如果local_long
被初始化内的synchronized
块,伪代码将是正确的.
如果long_val
是final
或volatile, then the
同步`块将不需要(至少为此目的).(出于不同的原因......)
1 - 基本上,您需要确保在"最终"生效之前没有其他线程使用常量字段.对于最终实例字段,这意味着在构造函数返回之前.对于最终的静态字段,这意味着在类初始化完成之前......当然注意到作为编译时常量的静态最终字段的处理方式不同.
2 - 我排除了使用反射来改变final
字段的边缘情况.这是邪恶的,它使所有关于可见性的保证无效.只是不要这样做.
如果long
常量声明为final
并且它们安全地发布为1,那么它们不需要声明为volatile
.final
字段的特殊属性在JLS 17.5中指定.
如果long
常量不是final
,则需要进行更深入的分析以确定它们是否真的是常量,以及初始化的结果是否对所有线程都可见.
声明(非final
)常量volatile
会实现这一点,但这不是一种好的(有效的)方法.深入分析(即仔细分析之前发生的关系)可能会发现这volatile
是不必要的.例如,如果一个线程初始化了常量,然后THEN调用start()
了所有使用它们的其他线程,那么(我认为)你可以逃避volatile
没有其他同步.
但是......宣布常数final
是更强大的方法2.
更新更新后的问题中的伪代码:
伪代码版本是不正确的,即使它在其他任何地方都没有改变.问题是是否所有线程都能保证看到初始值.问题是内存模型不需要long_val
由创建ThreadSafeClass
实例的线程刷新.这意味着另一个线程在调用时可以看到默认的初始值(零)calculate()
.
如果local_long
被初始化内的synchronized
块,伪代码将是正确的.
如果long_val
是final
或volatile, then the
同步`块将不需要(至少为此目的).(出于不同的原因......)
1 - 基本上,您需要确保在"最终"生效之前没有其他线程使用常量字段.对于最终实例字段,这意味着在构造函数返回之前.对于最终的静态字段,这意味着在类初始化完成之前......当然注意到作为编译时常量的静态最终字段的处理方式不同.
2 - 我排除了使用反射来改变final
字段的边缘情况.这是邪恶的,它使所有关于可见性的保证无效.只是不要这样做.