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

什么是单例设计模式

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

单例设计模式的八种方式

饿汉式

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

创建步骤:

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工厂等)

使用建议

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

不推荐使用 懒汉式

相关推荐
Java技术小馆5 分钟前
SpringBoot中暗藏的设计模式
java·面试·架构
xiguolangzi6 分钟前
《springBoot3 中使用redis》
java
Aniugel7 分钟前
JavaScript高级面试题
javascript·设计模式·面试
李菠菜13 分钟前
非SpringBoot环境下Jedis集群操作Redis实战指南
java·redis
不当菜虚困25 分钟前
JAVA设计模式——(四)门面模式
java·开发语言·设计模式
Niuguangshuo28 分钟前
Python设计模式:MVC模式
python·设计模式·mvc
m0Java门徒33 分钟前
面向对象编程核心:封装、继承、多态与 static 关键字深度解析
java·运维·开发语言·intellij-idea·idea
无心水1 小时前
【Java面试笔记:基础】8.对比Vector、ArrayList、LinkedList有何区别?
java·笔记·面试·vector·arraylist·linkedlist
Lei活在当下1 小时前
【现代 Android APP 架构】01. APP 架构综述
android·设计模式·架构
前端大白话1 小时前
震惊!90%前端工程师都踩过的坑!computed属性vs methods到底该怎么选?一文揭秘高效开发密码
前端·vue.js·设计模式