最全的单例模式详细介绍(上)

饿汉式单例模式

饿汉式单例模式,我们从名字就可以知道,他是类加载的时候就已经初始化好了单例,不管你用还是不用,它就在那里。因为是类加载的时候就已经初始化了,所以它不存在线程安全问题。在单例类较少的情况下,个人觉得非常推荐这个这种写法,简单,使用高效。

public class HungrySingleton {

    private static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return  hungrySingleton;
    }

}

懒汉式单例

1.顾名思义,我们也能知道,这个只有我们在用的时候,才会初始化对象。下面介绍最正确的懒汉式单例模式写法-双重锁检查(DCL)懒汉式单例模式

public class DubboCheckLazySingleton {

    // volatile解决指令重排序问题
    private static volatile DubboCheckLazySingleton lazySingleton ;

    private DubboCheckLazySingleton() {}

    public static DubboCheckLazySingleton getInstance() {

        if ( null == lazySingleton) {
            synchronized (DubboCheckLazySingleton.class) {
                if (null == lazySingleton) {
                    lazySingleton = new DubboCheckLazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}

静态内部类单例模式

1.静态内部类的单例模式,利用了类加载机制,既有饿汉式线程安全特性,又有懒汉式延迟加载的优点,所谓真的是一举两得。

public class InnerLazySingleton {

    private InnerLazySingleton() {}

    public static InnerLazySingleton getInstance() {
       return InnerLazy.innerLazySingleton;
    }

    // 每个修饰类的关键字都不能省的 static 是为了是单例空间共享,final 保证这个类不会被重写,覆盖
    private static class InnerLazy {
       private static final InnerLazySingleton innerLazySingleton = new InnerLazySingleton();
    }
}

类加载机制

我们从类加载机制介绍,上面的三个单例模式的原理。

一、什么是类加载机制

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制

二、什么情况会导致类的加载

① 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化
生成这 4 条指令的常见 Java 代码场景:
new:使用 new 关键字实例化对象
getstatic:读取一个类的静态字段
putstatic:设置一个类的静态字段
invokestatic:调用类的静态方法

如果大家对初始化不了解的,可以参考下面这篇文章:
Java初始化规则都不懂,搞什么开发!
② 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化

③ 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化

④ 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类

⑤ 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄时,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化

注意:接口的加载过程与类加载过程稍有不同,接口中不能使用 static{} 语句块,但编译器仍然会为接口生成 () 类构造器,用于初始化接口中所定义的成员变量。接口与类真正有所区别是情况 ③:当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口的初始化时,并不要求其父接口全部都完成了初始化,只有在真正用到了父接口的时候才会初始化

三、类加载器

可以通过一个类的全限定名来获取描述此类的二进制字节流,完成这一动作的代码模块被称为类加载器

3.1 类与类加载器
类加载器虽只用于实现类的加载动作,还可以与类一起来确立当前类在 Java 虚拟机中是否是唯一的,两个类被同一个类加载器加载,这两个类才相等

3.2 双亲委派模型
3.2.1 虚拟机划分
a. 从虚拟机角度划分
1.启动类加载器(Bootstrap ClassLoader),由 C++ 语言实现,是虚拟机自身的一部分
2.其它的类加载器,由 Java 语言实现,独立于虚拟机外部,全继承自抽象类 java.lang.ClassLoader

b. 从 Java 开发人员角度划分
1.启动类加载器
2.扩展类加载器
3.应用程序类加载器
4.自定义类加载器(如果有必要,可以自定义)

3.2.2 双亲委派模型
约束:双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。类加载器之间的父子关系一般不以继承关系实现,而是使用组合关系来复用父加载器的代码

工作过程:类加载器收到一个类加载的请求,自己不会先加载,而是把该请求委派给父类加载器,每一层的类加载器都是如此,因此最终该请求会被传送到顶层的启动类加载器中,只有当父类加载器无法完成加载请求(对应搜索范围内没有找到所需的类)时,子加载器才尝试自己去加载

好处:双亲委派模型可以保证系统中的类在各种类加载器环境中都是同一个类,即使用户自定义一个和系统同名的类,也不能被类加载器加载,保证了 Java 程序的稳定运作,因为无论哪一个类加载器要加载一个类,最终都是委派给最顶端的启动类加载器进行加载

3.2.3 破坏双亲委派模型
双亲委派模型不是强制性的约束模型,Java 中大部分的类加载器都遵循这个模型,但是有例外
1.双亲委派模型是在 JDK 1.2 之后引入的,类加载器是在 JDK 1.0 就已经存在。所以在 JDK 1.2 之前,用户可用继承 java.lang.ClassLoader 去重写 loadClass() 方法
2.当基础类要调用用户的代码时,父类加载器可以请求这类加载器去完成类加载的动作
3.对程序动态性的追求,如:代码热替换、代码热部署等

对三种模式原理解释

一.我们从类加载顺序来稍微解释一下静态内部类单例模式为什么有这么两个优点(以Spring框架为基础)。

1.在spring框架中,我们对每个类都进行了新建,也就是会对所有都用了关键字new。
按照类加载原则,我们对所有的静态成员变量初始化,如果没有指定初始化,就用默认值。
1饿汉式,我们指定了初始化,我们直接就创建了对象。所有用的时候直接就返回,初始化创建好的对象。因为在类加载的时候,底层创建类肯定是线程安全,所以不存在线程安全问题。

2.懒汉式,我们初始化时候,并没有指定初始化,所有默认的初始化值是null。我们用的时候(这个时候创建就有可能两个线程都用这个类,都创建),所以加了synchronized 关键字和判空,来保证线程安全和提供程序运行效率。

3.静态内部类,初始化时候,我们并不会初始化静态内部类,只有在静态内部类被调用的时候,我们才初始化,但是因为也属于一个类的加载,所以在底层保证了线程安全,所以也不存在线程安全,又只在我调用内部静态类的时候,才初始化。所以也拥有了懒汉式的优点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值