设计模式-创建型模式-单例模式详解

什么是单例设计模式

所谓 类的单例设计模式 ,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且 该类只提供一个取得该对象实例的静态方法。

单例设计模式的八种方式

饿汉式

在类加载时就创建对象,因此是线程安全的。
但是,有可能会造成资源浪费,因为 无论是否用到该对象,都会被创建。

创建步骤:

1、私有化构造器 : 防止外部类调用构造器进行对象创建;

2、类的内部直接new 一个对象;

3、提供一个 共有的 方法,返回 单例对象,供外部的类使用。

方式一:静态常量的方式初始化对象

java 复制代码
public class SingleTon {
    // 1、私有化构造函数
    private SingleTon(){}
    
    // 2、初始化静态变量
    private static final SingleTon instance = new SingleTon();
    
    // 3、提供静态方法返回对象
    public static SingleTon getInstance(){
        return instance;
    }
}

方式二:静态代码块的方式初始化对象

java 复制代码
public class SingleTon {
    // 1、私有化构造器
    private SingleTon(){}
    
    // 2、声明一个静态的对象
    private static final SingleTon singleTon;
    
    // 3、静态代码块中初始化对象
    static {
        singleTon = new SingleTon();
    }
    
    // 4、提供公共的静态方法,返回对象
    public static SingleTon getInstance(){
        return singleTon;
    }
}

验证代码

java 复制代码
public class SingleTonTest {
    public static void main(String[] args) {
        // 获取单例对象
        SingleTon singleTon1 = SingleTon.getInstance();
        
        // 再获取一次单例对象
        SingleTon singleTon2 = SingleTon.getInstance();
        
        // 判断是否是同一个对象
        System.out.println(singleTon1 == singleTon2);
        
        // 打印对象的hashCode,看两个对象的hashcoe码是否相同
        System.out.println("singleTon1.hashCode() = " + singleTon1.hashCode());
        System.out.println("singleTon2.hashCode() = " + singleTon2.hashCode());
    }
}

懒汉式

在用到对象的时候,才进行创建。

创建步骤:

1、私有化构造器 : 防止外部类调用构造器进行对象创建;

2、声明一个对象,但不进行实例化;

3、提供一个 共有的 方法,进行对象的实例化,并返回对象。(即 用到的时候才创建对象)

方式一 : 线程不安全

java 复制代码
public class SingleTon {
    // 1、私有化构造器
    private SingleTon(){}
    // 2、声明一个静态的对象
    private static SingleTon singleTon;
    // 3、提供公共的静态方法,返回对象
    public static SingleTon getInstance(){
        // 核心 : 判断对象是否为空,为空则创建对象,不为空则直接返回对象
        // 缺点 : 存在线程安全问题
        if (singleTon == null){
            singleTon = new SingleTon();
        }
        return singleTon;
    }
}

1、优点 : 在单线程下,起到了 懒加载 的效果;

2、缺点 : 在多线程下,当一个线程进入了 if (singleTon == null) 判断,还没创建对象时,另一个线程也进入了 if (singleTon == null) 判断,此时便会产生多个实例。所以,多线程环境下,此方式不适用。

3、结论 : 实际开发中,不推荐使用此方式。

方式二:线程安全-同步的方法

java 复制代码
public class SingleTon {
    // 1、私有化构造器
    private SingleTon(){}
    // 2、声明一个静态的对象
    private static SingleTon singleTon;
    // 3、提供公共的静态方法,返回对象,synchronized 保证线程安全
    public static synchronized SingleTon getInstance(){
        // 核心 : 判断对象是否为空,为空则创建对象,不为空则直接返回对象
        if (singleTon == null){
            singleTon = new SingleTon();
        }
        return singleTon;
    }
}

1、优点 : 解决了线程安全的问题;

2、缺点 : 使用 synchronized 关键字对方法进行加锁,每次获取对象,都要进行同步,极大的降低了代码的执行效率。

3、结论 : 在实际开发中,不推荐使用此中方式。

方式三:线程安全?-同步代码块

java 复制代码
public class SingleTon {
    // 1、私有化构造器
    private SingleTon(){}
    // 2、声明一个静态的对象
    private static SingleTon singleTon;
    // 3、提供公共的静态方法,返回对象
    public static SingleTon getInstance(){
        // 核心 : 判断对象是否为空,为空则创建对象,不为空则直接返回对象
        if (singleTon == null){
            // 同步代码块,线程安全? 
            // 但是上面的 if 判断条件没有线程安全,所以也 无法解决 线程安全问题
            synchronized (SingleTon.class){
                singleTon = new SingleTon();
            }
        }
        return singleTon;
    }
}

1、这种方式,由于 if (singleTon == null) 条件判断的原因,根本无法解决线程安全的问题!

2、结论 : 在实际开发中,不能使用这种方式。

双重检查

下面的方法上的 synchronized 关键字是没有的,抄代码的时候抄错了

是对 【懒汉式】的一种优化。
1、volatile 关键字 :确保变量的可见性 : 变量修改后,刷新 主存中的数据,这样线程就能获取最新的值。
2、synchronized 关键字:提供同步控制,确保同一时刻,只能有一个线程执行 被 synchronized 修饰的方法或代码块。 
java 复制代码
public class SingleTon {
    // 1、私有化构造器
    private SingleTon(){}
    // 2、声明一个静态的对象 : 使用 volatile 关键字修饰
    private static volatile SingleTon singleTon;
    // 3、提供公共的静态方法,返回对象
    public static SingleTon getInstance(){
        // 核心 : 判断对象是否为空,为空则创建对象,不为空则直接返回对象
        if (singleTon == null){
            // 核心 : 同步代码块
            synchronized (SingleTon.class){
                // 核心 : 二次判断对象是否为空
                if (singleTon == null){
                    singleTon = new SingleTon();
                }
            }
        }
        return singleTon;
    }
}

1、优点 : Double-Check 的概念是多线程开发中经常使用到的。目的是 确保线程安全;

2、优点 : 实例化对象的代码只用执行一次,后面再次访问时,判断条件 if(singleTon == null)

为 false,直接return 对象,避免了重复进行实例化的动作,确保了单实例的效果;

3、优点 :实现了懒加载的功能,效率较高。

4、结论 : 实际开发中,推荐使用。

静态内部类

静态内部类 : 外部类的一个成员,使用 static 修饰。
这种方式,是 将 单例类的 单例对象,作为 静态内部类的一个静态成员变量。
使用 类加载的机制 确保 线程安全和 单例。
java 复制代码
public class SingleTon {
    // 1、私有化构造器
    private SingleTon(){}
    // 2、声明一个静态内部类
    private static class SingleTonHolder{
        // 静态内部类中 有一个 单例的对象
        private static SingleTon singleTon = new SingleTon();
    }
    // 3、提供公共的静态方法,返回对象
    public static SingleTon getInstance(){
        return SingleTonHolder.singleTon;
    }
}

1、使用了 类加载的机制 来保证初始化实例时只有一个线程。

2、在 外部单例类 被加载时不回立即实例化 静态内部类;

3、在 调用 getInstance 方法时,会加载 静态内部类,完成 单例对象的实例化。

4、类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程无法进入。

5、优点 :线程安全,利用静态内部类的特点实现延迟加载。

6、结论 : 推荐使用。

枚举

枚举类 : 
  1、对象个数有限 且 确定;
  2、类的构造方法 私有化 : private 修饰 - private 可以省略;
  3、类的属性 : private final  xxx xx;

一句话 : 枚举类 太适合搞单例了。
java 复制代码
public enum SingleTonEnum {

    // 单例对象
    INSTANCE("张校长", 48);

    // 对象属性
    private final String name;
    private final int age;
    // 构造方法
    SingleTonEnum(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
}

1、优点 : 避免了多线程同步的问题,而且还能防止反序列化重新创建新的对象。

2、结论 : 推荐使用。

小结

使用场景

1、单例模式 保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;

2、当想实例化一个单例类的时候,必须要记住,使用相应的获取对象的方法,而不是使用new;

3、单例模式的使用场景 : 需要频繁的创建和销毁的对象、创建对象时比较耗时或耗费资源过多(即:重量级对象),但是又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

使用建议

推荐使用 双重检查静态内部类枚举 的方式的 单例模式。

不推荐使用 懒汉式

相关推荐
paterWang3 分钟前
小程序-基于java+SSM+Vue的优购电商小程序设计与实现
java·vue.js·小程序
Annaka91817 分钟前
蓮说Java | Java中的“.”操作符与“->”操作符在使用上与C语言中的有何区别?
java·c语言·python
m0_7482550222 分钟前
Chrome webdriver下载-避坑
java
重生之Java开发工程师42 分钟前
⭐设计模式—策略模式
java·设计模式·面试·策略模式
南宫生1 小时前
力扣-图论-7【算法学习day.57】
java·学习·算法·leetcode·深度优先·图论
Oceanside_yh1 小时前
Macbookpro M1 IDEA中安装mysql
java·mysql·intellij-idea
坚持不懈的大白1 小时前
Java:集合(List、Map、Set)
java·list·set·map·collection
就爱学编程1 小时前
重生之我在异世界学智力题(6)
android·java·数据库
Allen Bright1 小时前
RabbitMQ中点对点(Point-to-Point)通讯方式的Java实现
java·rabbitmq
java菜鸡加油1 小时前
代码随想录-算法训练营-番外(图论02:岛屿数量,岛屿的最大面积)
java·算法·leetcode·深度优先·力扣·图论