设计模式之单例模式详解

单例模式

描述:单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

核心特点

  1. 单例类只有一个实例对象
  2. 该单例对象必须由单例类自行创建
  3. 单例类对外提供 一个访问该单例的全局访问点

实现方式

​ 通常,普通类的构造函数是公有的,外部类可以通过"new 构造函数()"来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。

角色功能

  • 单例类:包含一个实例且能自行创建这个实例的类。
  • 访问类:使用单例的类。

类图:

优点:

  • 单例模式可以保证内存里只有一个实例,避免重复创建和销毁,减少了内存的开销
  • 可以避免对资源的多重占用。
  • 单例模式设置全局访问点,可以优化和共享资源的访问。

缺点:

  • 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
  • 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
  • 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。

实现代码

实现要点:private修饰默认构造函数、private static修饰实例对象、public static修饰获取实例的方法

懒汉式单例

该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例,第一次用到才创建实例,所以叫懒汉(这其实是优点)。

特点:

  • 实现简单
  • 首次使用时才创建对象实例(实现了懒加载)
  • 若想实现线程安全则必须使用synchronized,效率较低
java 复制代码
public class LazySingleton {
    private static volatile LazySingleton instance = null;    //保证 instance 在所有线程中同步

    private LazySingleton() {
    }    //private 避免类在外部被实例化

    public static synchronized LazySingleton getInstance() {
        //getInstance 方法前加同步
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

双重校验机制

属于懒汉式的拓展实现

特点:

  • 能实现懒加载,也是线程安全的,且效率较高
  • 实现复杂,需要两次校验(进入同步块后还要校验一次)
java 复制代码
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

问:为什么synchronized语句不把第一个if判断语句要包括进去?

答:包括进去就和synchronized方法没区别了,效率很低。

问:为什么synchronized语句块创建实例对象前还要再判断一次实例是否被创建?

答:为了保证实例只被创建一次。如果初始实例为null时,有多个线程进入到第一个判断语句并竞争锁,拿到锁的线程如果已经创建了实例后,后续拿到锁的线程就不能再创建了,所以要再判断一次。换句话说synchronized去修饰实例创建语句时候,只能保证同时刻只有一个在创建,而不能保证后续没有别的线程去创建。

饿汉式单例

该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了,它是线程安全的,可以直接用于多线程而不会出现问题。

特点:

  • 类加载时就创建好对象实例,也就是不管是否用到都初始化,浪费内存
  • 天然的线程安全,不需要使用到锁
java 复制代码
public class HungrySingleton {
    //类加载时就会创建实例化对象
    private static HungrySingleton instance = new HungrySingleton();
    
    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return instance;
    }
}

静态内部类

属于饿汉式的拓展,解决饿汉式无法实现懒加载问题,能实现和双重校验机制一样的效果。

在单例类里面创建一个SingletonHolder内部静态类,外部的单例类被加载时不会立马初始化内部静态类,只有去调用内部静态类的静态成员或静态方法才会触发类的加载,也能够实现第一次用到时才加载,也就是懒加载。

java 复制代码
public class Singleton {  
    //内部静态类,单例类加载时不会立马初始化,只有访问内部静态变量时才初始化
    private static class SingletonHolder {  
    	private static Singleton INSTANCE = new Singleton();  
    }  
    //空的构造方法
    private Singleton (){
        
    }  
    public static Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

枚举式

单例模式的最佳方法,非常简洁、高效、线程安全、支持序列化机制、无法被反射机制破解(其它方式都可以)。

java 复制代码
    public enum Singleton {
        INSTANCE;  // 枚举里的属性相当于Singleton的实例
        private Person instance;

        Singleton() {// 私有构造函数 默认是 private
            instance = new Person(); // 在构造函数中完成实例化操作
        }

        public static Person getInstance() {// 提供公有方法对其访问
            return instance;
        }
    }
    // 在外部使用Singleton.INSTANCE.getInstance();来调用
}

如何选?

  • 懒汉式要么线程不安全,要么效率低,要么实现复杂,不建议使用。
  • 如果单例对象占用资源大,需要懒加载 ,建议用内部静态类方式(优于懒汉式)。
  • 如果单例对象占用资源小,不需要懒加载 ,建议用枚举式(优于饿汉式)。

应用场景

对于 Java来说,单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。

  • 某类需要频繁创建销毁 ,且资源消耗又比较大 的对象,如线程池数据库连接池、配置文件、日志管理等,单例可提高性能和节省资源。
  • 当对象需要多线程共享访问的场合。由于单例模式只允许创建一个对象,能够保证线程安全性且达到共享目的。
  • 外部和内部资源管理 。对于管理外部打印机、回收站内部属性文件的系统,单例模式可以确保这些资源被系统中的唯一实例所管理。
  • 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
相关推荐
为将者,自当识天晓地。17 分钟前
c++多线程
java·开发语言
daqinzl25 分钟前
java获取机器ip、mac
java·mac·ip
激流丶41 分钟前
【Kafka 实战】如何解决Kafka Topic数量过多带来的性能问题?
java·大数据·kafka·topic
Themberfue1 小时前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
让学习成为一种生活方式1 小时前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
晨曦_子画1 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
Heavydrink2 小时前
HTTP动词与状态码
java
ktkiko112 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田2 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue