详解:单例模式中的饿汉式和懒汉式

单例模式是一种常用的设计模式,其目的是确保一个类只有一个实例(对象),并提供一个全局访问点。单例模式有两种常见的实现方式:饿汉式和懒汉式。

一、饿汉式

饿汉式在类加载时就完成了实例化。因为类加载是线程安全的,所以在多线程环境下也是安全的。这种方式比较简单,但是由于实例在类加载时就创建,即使没有被使用,也会占用内存资源。

步骤:

1.私有化构造函数

解释 :当私有化构造函数那么其他类 就不能创建该类的对象,相当于该类的一切非静态的成员在其他类中 都访问不了了!!!当你使用new创建对象时会调用其构造方法,因为构造函数私有化了,所以创建不了对象,创建不了对象相当于一切非静态的成员在其他类中都访问不了。

2.创建私有的静态类的对象

解释 :为什么是私有的?防止其他类直接调用对象从而破坏单例的唯一性。为什么是静态的?如果不是静态的属性,那么其他类无法创建对象进行访问。

3.创建公共的静态get方法获取静态类的对象!!!

解释:该操作类似于封装私有的成员变量然后只能通过公共的set和get方法来获取,但是改操作是一个静态的方法,静态的方法只能调用静态的成员变量不能直接调用非静态的成员。

代码示例:

public class Person {
    private String name;
    // 在类加载时就创建实例
    private static Person person = new Person("小明");
    // 私有构造函数,防止外部实例化
    private Person(String name){
        this.name=name;
    }
    // 提供全局访问点  
    public static Person getInstance(){
        return person;
    }

    @Override
    public String toString() {
        return name;
    }
}
class Test{
    public static void main(String[] args) {
      Person person1 = Person.getInstance();
      Person person2 = Person.getInstance();
      System.out.println(person1);
      System.out.println(person2);
      System.out.println(person1==person2);
    }
}

结果如下:

小明
小明
true

解释:这段代码,我们只能通过静态的getInstance()方法来获取Person对象,并且只能获取一个对象,因为在其他类(比如Test类)创建不了Person对象而只能获取一个Person对象,当我们再次调用静态的getInstance()方法来获取Person对象时,本质上是获取同一个对象,所以通过"=="进行比较得到的结果是true。

疑问1:为什么输出的是小明?

答:因为Person类重写了toString方法。

疑问2:为什么name属性不是静态的属性?

答:在你不需要调用name属性时不需要写成静态的属性。

疑问3:为什么实例(对象)在类加载时就创建?

答:因为在类加载时会进行静态属性的初始化和执行静态代码块。

参考文献:类什么时候加载?-CSDN博客

优点:

  • 线程安全:因为实例在类加载时就创建,天然线程安全。
  • 实现简单:代码量少,容易理解。

缺点:

  • 资源浪费:如果实例从未被使用,仍然会占用内存。

二、懒汉式

懒汉式在第一次调用getInstance()方法时才创建实例。这种方式延迟了实例化,节省了资源,但需要考虑线程安全问题。

代码如下:

public class Person {
    private String name;
    // 声明实例,但不立即创建  
    private static Person person;
    // 私有构造函数,防止外部实例化
    private Person(String name){
        this.name=name;
    }
    // 提供全局访问点但非线程安全
    public static Person getInstance(){
        if (person==null){
            person = new Person("小明");
        }
        return person;
    }

    @Override
    public String toString() {
        return name;
    }
}
class Test{
    public static void main(String[] args) {
      Person person1 = Person.getInstance();
      Person person2 = Person.getInstance();
      System.out.println(person1);
      System.out.println(person2);
      System.out.println(person1==person2);
    }
}

结果如下(和饿汉式代码的结果一样):

小明
小明
true

解释: 与饿汉式的区别是懒汉式在第一次调用getInstance()方法时才创建实例并不是类加载时就创建实例对象,创建实例对象时首先判断对象是否存在,不存在则创建对象,存在则返回已存在的对象。

线程安全问题

在懒汉式单例模式中,如果多个线程同时调用getInstance()方法,并且此时实例尚未创建(即instance变量为null),那么这些线程都可能进入创建实例的代码块。如果没有适当的同步机制,就可能导致多个线程同时创建实例,从而违反单例模式的原则。

优点:

  • 延迟实例化,节省资源。

缺点:

  • 实现复杂:需要考虑线程安全问题。

饿汉式和懒汉式的选择取决于具体的应用场景。如果单例对象较大且创建过程耗时,或者类加载时间较长,可以考虑使用懒汉式。如果单例对象较小且创建过程简单,或者类加载时间可以接受,可以考虑使用饿汉式。同时,懒汉式需要考虑线程安全问题,而饿汉式则不需要。

相关推荐
小蜗牛慢慢爬行5 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试
Algorithm157615 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang
shinelord明24 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
新手小袁_J29 分钟前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11
呆呆小雅30 分钟前
C#关键字volatile
java·redis·c#
Monly2131 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu32 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee202132 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
Ttang2333 分钟前
Tomcat原理(6)——tomcat完整实现
java·tomcat
goTsHgo34 分钟前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc