JVM(二)------ 类加载、初始化与单例模式的联系

首先我们要知道,单例模式有饿汉式和懒汉式两种

(一)饿汉单例

先说饿汉式:

复制代码
public class SingletonEager {
    // ① 类加载时立即创建实例
    private static final SingletonEager instance = new SingletonEager();

    // ② 私有构造函数,外部无法 new
    private SingletonEager() {
        System.out.println("构造函数被调用");
    }

    // ③ 提供全局访问点
    public static SingletonEager getInstance() {
        return instance;
    }
}

饿汉单例模式关键就是:类加载时就创建对象实例 + 每次访问都返回同一个对象实例

①类加载时就创建对象实例

前者好保证,就是代码中的①,创建一个static final修饰的对象实例,那么这个类在加载时就会创建对象实例,在准备阶段由于这是一个new的对象,所以赋默认值:null;

②每次访问都返回同一个对象实例

那么对于后者,就有两个维度:

  1. 首先要保证外部访问时对象已经创建好,不能将创建对象的权力交给外部(构造函数私有)
  2. 其次就是每次请求都返回之前创建好的对象的引用

对于第2点,流程是这样的:

  1. 暴露一个getInstance()函数,外部想要获得对象只能通过这种手段;
  2. 当外部调用时,类才真正执行初始化(第一次调用静态方法),static final SingletonEager instance = new SingletonEager();的过程中,jvm会保证类的初始化(<clinit>(静态初始化))是线程安全并且只进行一次的,所以这个对象只会被创建一个;
  3. 最后,return instance;返回的都是这同一个对象的引用,保证了单例

这就是饿汉单例;

(二)懒汉单例

顾名思义,就是使用到的时候再创建,饿汉单例由于一开始就创建并赋值,所以类加载的时候就会赋默认值,但是饿汉单例只是声明,所以类加载的时候就不会创建这个对象;

复制代码
public class SingletonLazy {
    private static SingletonLazy instance; // 还没创建

    private SingletonLazy() {
        System.out.println("Lazy 构造函数被调用");
    }

    public static SingletonLazy getInstance() {
        if (instance == null) { // 第一次使用时才创建
            instance = new SingletonLazy();
        }
        return instance;
    }
}

而等到外部调用静态方法时,那么执行类的初始化,但是由于懒汉单例只是声明对象没有要赋值,所以jvm的执行**<clinit>并不包含对象的赋值,因此就需要我们手动判断对象只创建一个(但是!!! 先判断后执行,这个动作不是原子性的,所以不是线程安全的)。**

解决方法:

1.加synchronized锁:安全但是效率低

复制代码
public class SingletonLazySafe {
    private static SingletonLazySafe instance;

    private SingletonLazySafe() {
        System.out.println("SingletonLazySafe 构造函数被调用");
    }

    public static synchronized SingletonLazySafe getInstance() {
        if (instance == null) {
            instance = new SingletonLazySafe();
        }
        return instance;
    }
}

(三)懒汉单例----线程安全

使用静态内部类的方式,为什么??? 想一下懒汉单例为什么线程安全?? 因为其静态变量的赋值借助了JVM在进行类初始化(真正初始化静态变量,静态方法)的时候,线程安全是由JVM保证的,所以我们可以通过第二种:将对象创建放在静态内部类中,那么这个内部类在类加载时不会初始化,只在调用到类的静态方法时被初始化,原理有些类似于静态变量:

由于JVM调用**<clinit>只会执行一次且是线程安全的,那么这个实例只有一个并且创建过程中线程安全。**

复制代码
public class SingletonInner {
    private SingletonInner() {
        System.out.println("SingletonInner 构造函数被调用");
    }

    // 静态内部类,只有在第一次调用 getInstance() 时才加载
    private static class Holder {
        private static final SingletonInner INSTANCE = new SingletonInner();
    }

    public static SingletonInner getInstance() {
        return Holder.INSTANCE;
    }
}
相关推荐
雨落在了我的手上3 小时前
初识java(九):类和对象(⼀)
java·开发语言
是码龙不是码农4 小时前
数据库主键选型:为什么别用自增 ID?
java·数据库
北风toto4 小时前
Jenkins新手入门安装插件全报错
java·运维·jenkins
罗超驿4 小时前
20.MySQL事务隔离级别示例详解(脏读、不可重复读、幻读)
java·数据库·mysql·面试
Dicky-_-zhang4 小时前
KubeEdge边缘部署实践
java·jvm
码银4 小时前
在若依中如何新建一个模块(图文教程)
java·javascript
Yeats_Liao4 小时前
物联网接入层技术剖析(四):当epoll遇见MQTT
java·linux·服务器·网络·物联网·架构
一条大祥脚4 小时前
Codeforces Round 1099 (Div. 2) 构造|贪心|图论|还原数组
java·算法·图论
yaoxin5211234 小时前
414. Java 文件操作基础 - 批量压缩与索引:将154首十四行诗高效存储为带目录的二进制文件
java·windows·python
超梦dasgg4 小时前
详细讲解:WebMvcConfigurer 接口
java·开发语言·spring