我正在浏览 Java 内存模型视频演示,作者说它Static Lazy Initialization
比它更好用Lazy Initialization
,但我不清楚他想说什么。
我想接触社区,如果有人能解释简单的 java 代码示例之间的区别Static Lazy Initialization
,我将不胜感激。Lazy Initialization
我正在浏览 Java 内存模型视频演示,作者说它Static Lazy Initialization
比它更好用Lazy Initialization
,但我不清楚他想说什么。
我想接触社区,如果有人能解释简单的 java 代码示例之间的区别Static Lazy Initialization
,我将不胜感激。Lazy Initialization
那么这两种实现都可以是静态的,所以这是第一个误解。本视频中的演示者正在解释如何利用类初始化的线程安全性。
类初始化本质上是线程安全的,如果您可以在类初始化时初始化对象,则对象创建也是线程安全的。
这是一个线程安全的静态初始化对象的示例
public class MySingletonClass{
private MySingletonClass(){
}
public static MySingletonClass getInstance(){
return IntiailizationOnDemandClassholder.instance;
}
private static class IntiailizationOnDemandClassHolder{
private static final MySingletonClass instance = new MySingletonClass();
}
}
这里重要的是要知道,MySingletonClass 实例变量在getInstance()
被调用之前永远不会被创建和/或初始化。同样,由于类初始化是线程安全的,instance
变量 ofIntiailizationOnDemandClassholder
将被安全地加载一次,并且对所有线程都是可见的。
回答您的编辑取决于您的其他类型的实现。如果您想进行双重检查锁定,您的实例变量将需要是易失的。如果您不想要 DCL,那么您将需要每次同步访问您的变量。下面是两个例子:
public class DCLLazySingleton{
private static volatile DCLLazySingleton instance;
public static DCLLazySingleton getInstace(){
if(instance == null){
synchronized(DCLLazySingleton.class){
if(instance == null)
instance=new DCLLazySingleton();
}
}
return instance;
}
和
public class ThreadSafeLazySingleton{
private static ThreadSafeLazySingleton instance;
public static ThreadSafeLazySingleton getInstance(){
synchronized(ThreadSafeLazySingleton.class){
if(instance == null){
instance = new ThreadSafeLazySingleton();
}
return instance;
}
}
最后一个示例需要在实例的每个请求上获取锁。第二个示例需要在每次访问时进行 volatile-read(可能便宜或不便宜,取决于 CPU)。
第一个示例将始终锁定一次,而与 CPU 无关。不仅如此,每次读取都是正常的,无需担心线程安全。我个人喜欢我列出的第一个例子。
我认为演示文稿中的作者提到了这样一个事实,即在第一次使用包含该字段的类时,静态字段只会以线程安全的方式初始化一次(这是由 JMM 保证的):
class StaticLazyExample1 {
static Helper helper = new Helper();
static Helper getHelper() {
return helper;
}
}
这里helper
的字段将在第一次使用StaticLazyExample1
类时初始化(即在构造函数或静态方法调用时)
还有 Initialization On Demand Holder 成语,它基于静态惰性初始化:
class StaticLazyExample2 {
private static class LazyHolder {
public static Helper instance = new Helper();
}
public static Helper getHelper() {
return LazyHolder.instance;
}
}
这里只有在第一次调用静态方法Helper
时才会创建一个实例。StaticLazyExample2.getHelper()
由于静态字段的初始化保证,此代码保证是线程安全且正确的;如果在静态初始化程序中设置了一个字段,则可以保证它对访问该类的任何线程正确可见。
更新
两种类型的初始化有什么区别?
静态延迟初始化为静态字段提供了高效的线程安全延迟初始化,并且具有零同步开销。另一方面,如果你想懒惰地初始化一个非静态字段,你应该这样写:
class LazyInitExample1 {
private Helper instance;
public synchronized Helper getHelper() {
if (instance == null) instance == new Helper();
return instance;
}
}
或者使用双重检查锁定习语:
class LazyInitExample2 {
private volatile Helper helper;
public Helper getHelper() {
if (helper == null) {
synchronized (this) {
if (helper == null) helper = new Helper();
}
}
return helper;
}
}
我应该提到它们都需要显式同步并且与静态延迟初始化相比会带来额外的时间开销吗?
值得注意的是,最简单的线程安全静态延迟初始化是使用 thisenum
有效,因为静态字段的初始化是线程安全的,并且无论如何都会延迟加载类。
enum ThreadSafeLazyLoadedSingleton {
INSTANCE;
}
使用延迟加载值的类是 String。hashCode 仅在第一次使用时计算。之后使用缓存的 hashCode。
我不认为你可以说一个比另一个更好,因为它们并不是真正可以互换的。
当然,在这里参考会很好。他们都有相同的基本思想:如果不需要,为什么要分配资源(内存、cpu)?相反,将这些资源的分配推迟到真正需要它们之前。这在密集的环境中可以很好地避免浪费,但如果您现在需要结果并且不能等待,则可能会非常糟糕。添加一个“惰性但谨慎”的系统非常困难(检测停机时间并在空闲时间运行这些惰性计算的系统。)
这是延迟初始化的示例。
class Lazy {
String value;
int computed;
Lazy(String s) { this.value = s; }
int compute() {
if(computed == 0) computed = value.length();
return computed;
}
}
这是静态延迟初始化
class StaticLazy {
private StaticLazy staticLazy;
static StaticLazy getInstance() {
if(staticLazy == null) staticLazy = new StaticLazy();
return staticLazy;
}
}
区别在于您实现延迟初始化的机制。Static Lazy Initialization
我假设演示者的意思是这个解决方案依赖于 JVM 与任何版本的 Java 兼容(请参阅 Java 语言规范的 12.4 类和接口的初始化)。
Lazy Initialization
可能意味着这个问题的许多其他答案中描述的延迟初始化。这种初始化机制对 JVM 做出了假设,这些假设在 Java 5 之前不是线程安全的(因为 Java 5 具有真正的内存模型规范)。