单例模的懒加载分析

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6


写在前面:之前也学习过单例模式,也看了很多的视频,也写过一点点总结。但是还是有疑问,特别是对于懒加载的概念。很多视频都是说,基于饿汉式的不足,引入了懒汉式懒加载的特带你。


对于最初的我来说,我仅仅靠记忆来记忆特性,但是没有理解它的具体含义:饿汉式嘛,饿了就要吃饭,不会等,所以就直接加载;懒汉式嘛,太懒惰了,所以有吃的也不一定会去马上吃…


基于此,这篇单例模式博文进一步讲讲懒汉式和饿汉式的区别,希望对你和我都有帮助!!!



文章目录

  • ​​1、单例模式概述​​
  • ​​2、饿汉式​​
  • ​​1.1、直接实例化(静态变量版)​​
  • ​​1.2、使用枚举类型​​
  • ​​1.3、1.1和1.2的测试代码​​
  • ​​3、懒汉式​​
  • ​​3.1、适合单线程的懒汉式​​
  • ​​3.2、适合多线程的懒汉式​​
  • ​​3.3、静态内部类的方式创建​​
  • ​​4、总结​​





1、单例模式概述

请牢记这句话,后文我也会反复出现,这是理解懒加载的本质:一个类要创建实例需要先加载并初始化该类,可以简单的理解为,一个类的实例的最终形态需要先进行初始化,再进行实例化

想更加深刻理解这个概念的可以参考我的另一篇博文:​​类的初始化和实例化之间的关系​​,希望对你有帮助

几种常见的形式

直接分为饿汉式和懒汉式两种来记忆

  • 饿汉式:直接创建对象,不存在线程安全问题
  1. 直接实例化饿汉式(简洁直观)
  2. 枚举式(最简洁)
  3. 静态代码块饿汉式(适合复杂实例化)
  • 懒汉式:延迟创建对象
  1. 线程不安全(适合创建单线程)
  2. 线程安全(适合多线程)
  3. 静态内部类形式(适合多线程)



2、饿汉式

我们的类会先进行初始化,再进行实例化。初始化的时候是先加载静态变量静态代码块

回到我们的饿汉式单例本身。饿汉式是指无论我们是否需要这个对象,在类进行初始化的时候都进实例化操作。于是我们可以联想到,该饿汉式单例的创建,一定和静态变量静态代码块有着某种联系



1.1、直接实例化(静态变量版)

创建步骤:

  1. 构造器私有化
  2. 自行创建,并且使用静态变量进行保存
  3. 向外提供这个实例
  4. 强调这是一个单例,我们可以使用final进行修饰

当我们在实例化一个类的时候,会先进行初始化,再进行实例化。由于我们的INSTANCE常量被static修饰,所以在我们进行类初始化的时候,就会完成对应的实例化操作。

package pers.mobian.questions02;

public class Singleton01 {
//最简单的单例模式的创建
public static final Singleton01 INSTANCE = new Singleton01();
private Singleton01(){

}
}



1.1方式的改良版(静态代码块)

该方式在本质上与第一种没有太大的区别,但是我们这种方式更加的灵活。

我们可以假设我们现在需要实例化的单例方式,是一个带参数的构造方法,并且对应的构造方法的参数来自我们的配置文件,我们就可以在static的静态代码块中加载对应的配置文件信息。

package pers.mobian.questions02;

public class Singleton03 {
public static final Singleton03 INSTANCE;

static {
//加载对应的配置文件信息,用于满足单例模式创建时的需求
xx = new ConfigurationFile("xxx");
INSTANCE = new Singleton03(xx)
}

private Singleton03() {

}
}



1.2、使用枚举类型

注意事项:

  1. 枚举类型表示该类型的对象是有限的几个
  2. 我们可以限定为一个,就可以成为我们的单例
  3. 使用枚举的方式,可以防止外部使用反射将其内部结构破坏
package pers.mobian.questions02;

public enum Singleton02 {
//调用它就能创建对象实例
INSTANCE;
}


1.3、1.1和1.2的测试代码

单例模的懒加载分析_实例化



3、懒汉式

如果我们的类中存在很多会开辟大空间的数组,我们的类仅仅来在初始化的阶段就会附带实例化对象。这就会出现,我们还并未使用该类,对应的所需的空间就被创建了,继而造成空间资源的浪费。


懒汉加载的本质是:当我们加载一个类的时候,是先进行类的初始化,再进行实例化。初始化的时候会执行静态变量静态代码块,使用懒汉式的方式创建单例模式,则是使用静态方法完成对应的创建工作,而该工作不在类的初始化阶段,而是在类的实例化阶段才完成,继而达到了我们所说的懒加载。



3.1、适合单线程的懒汉式

延迟创建这个类的实例:

  1. 构造器私有化
  2. 用一个静态变量保存这个唯一的实例
  3. 提供一个静态方法,获取这个实例对象
package pers.mobian.questions02;

public class Singleton04 {
private static Singleton04 INSTANCE;

private Singleton04() {
}

//使用静态方法,避免该单例模式在初始化的时候就完成加载
public static Singleton04 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton04();
}
return INSTANCE;
}
}

当然该方式存在一个天然的弊端,那就是在多线程情况下是会出现资源抢夺情况的。所以我们在多线程情况下又会引入一种新的懒汉式的适合多线程的单例模式



3.2、适合多线程的懒汉式

我们能想到的最直接的方式就是添加一个synchronized锁

package pers.mobian.questions02;

public class Singleton05 {
private static Singleton05 INSTANCE;

private Singleton05() {
}

public static Singleton05 getInstance() {
//直接添加一把锁
synchronized (Singleton05.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton05();
}
return INSTANCE;
}
}
}

由于添加锁的方式,太过于直接,为了争取效率的最大化,我们往往使用改进后的加锁方式。也就是常说的双重检测锁模式(Double Check Lock)

package pers.mobian.questions02;

public class Singleton06 {
//引入volatile关键字,防止指令重排
private volatile static Singleton06 INSTANCE;
private Singleton06() {
}

public static Singleton06 getInstance() {
//再添加一把锁
if (INSTANCE == null) {
synchronized (Singleton06.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton06();
}
}
}
return INSTANCE;
}
}



3.3、静态内部类的方式创建

1.在内部类被加载和初始化时,才会创建INSTANCE实例

2.静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的

3.因为是在内部类加载和初始化时创建的,所以该创建方式是线程安全的

package pers.mobian.questions02;

public class Singleton07 {
private Singleton07() {

}

private static class Inner {
private static final Singleton07 INSTANCE = new Singleton07();
}

public static Singleton07 getInstance() {
return Inner.INSTANCE;
}

}



4、总结



使用饿汉式创建:枚举类最简单

使用懒汉式创建:静态内部类最简单

具体使用哪种创建方式,根据我们的使用场景而定。


请牢记这句话,这是理解懒加载的本质:一个类要创建实例需要先加载并初始化该类,可以简单的理解为,一个类的实例的最终形态需要先进行初始化,再进行实例化


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6