我正在观看Java内存模型视频演示,作者说与懒加载
相比,使用静态延迟初始化
更好,但我不太清楚他想表达什么。
我想向社区寻求帮助,并希望有人能用简单的Java代码例子来解释静态延迟初始化
和懒加载
之间的区别。
参考资料:高级编程主题 - Java内存模型
我正在观看Java内存模型视频演示,作者说与懒加载
相比,使用静态延迟初始化
更好,但我不太清楚他想表达什么。
我想向社区寻求帮助,并希望有人能用简单的Java代码例子来解释静态延迟初始化
和懒加载
之间的区别。
参考资料:高级编程主题 - Java内存模型
首先,这两种实现方式都可以是静态的,这就是第一个误解。视频中的演讲者正在解释如何利用类初始化的线程安全性。
类初始化本质上是线程安全的,如果您可以在类初始化时初始化对象,则对象创建也是线程安全的。
以下是一个线程安全的静态初始化对象的示例:
public class MySingletonClass{
private MySingletonClass(){
}
public static MySingletonClass getInstance(){
return IntiailizationOnDemandClassholder.instance;
}
private static class IntiailizationOnDemandClassHolder{
private static final MySingletonClass instance = new MySingletonClass();
}
}
在这里需要知道的是,只有在调用 getInstance()
方法时,MySingletonClass 实例变量才会被创建和初始化。并且由于类初始化是线程安全的,因此 InitializationOnDemandClassholder 的 instance
变量将安全地加载一次,并对所有线程可见。
回答你的修改取决于你的其他实现方式。如果您想要进行双重检查锁定,则需要使实例变量成为 volatile。如果您不想使用 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如何。不仅如此,每次读取都将是普通的,无需担心线程安全。我个人喜欢我列出的第一个示例。
IntiailizationOnDemandClassHolder
示例是线程安全的。虽然您是正确的,new
不一定是线程安全的,但类加载是线程安全的。 new
赋值发生在释放类加载器锁之前。第二个示例中,new
是原子性的,因为该字段是volatile
。最后,第三个示例是线程安全的,因为new
赋值发生在释放类锁之前和任何随后获取该锁的操作之前。 - John Vintclass StaticLazyExample1 {
static Helper helper = new Helper();
static Helper getHelper() {
return helper;
}
}
在 StaticLazyExample1
类第一次使用时(即构造函数或静态方法调用时),helper
字段会被初始化。
还有一种基于静态延迟初始化的 Initialization On Demand Holder 模式:
class StaticLazyExample2 {
private static class LazyHolder {
public static Helper instance = new Helper();
}
public static Helper getHelper() {
return LazyHolder.instance;
}
}
当首次调用StaticLazyExample2.getHelper()
静态方法时,仅创建一个Helper
实例。由于静态字段的初始化保证,此代码保证是线程安全和正确的;如果在静态初始化器中设置了字段,则保证对访问该类的任何线程正确可见。
更新
这两种初始化方式有什么区别?
静态延迟初始化提供高效的线程安全延迟初始化static
字段,并具有零同步开销。另一方面,如果您希望惰性初始化非静态字段,则应编写以下内容:
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;
}
}
LazyInitExample
的第一个实例,它是线程安全的,因为 instance
字段是在同步块中写入和读取的。也许我最好从 getInstance
方法中删除 static
修饰符,以免让你感到困惑。 - Idolon值得注意的是,最简单的线程安全静态懒加载初始化方法是使用enum
。这是因为静态字段的初始化是线程安全的,并且类本身也是惰性加载的。
enum ThreadSafeLazyLoadedSingleton {
INSTANCE;
}
使用惰性加载值的类是字符串。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;
}
}
区别在于您实现延迟初始化的机制。通过“静态延迟初始化”,我认为演示者指的是this solution,它依赖于JVM遵守任何版本的Java(请参见Java语言规范的12.4类和接口的初始化)。
“懒惰初始化”可能意味着许多其他答案中描述的懒惰初始化。这种初始化机制对JVM做出了不安全的线程假设,直到Java 5才具有真正的内存模型规范。
懒加载只是一个花哨的名字,用于在实际需要时初始化类。
简单来说,懒加载是一种软件设计模式,其中对象的初始化仅在实际需要时发生,而不是在之前进行,以保持使用的简单性并提高性能。
当对象创建的成本非常高且对象的使用非常罕见时,懒加载是必不可少的。因此,在这种情况下,值得实现懒加载。懒加载的基本思想是在需要时加载对象/数据。
来源: https://www.geeksforgeeks.org/lazy-loading-design-pattern/