设计模式之单例模式

单例模式

1.单例模式的概述

1.单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式


2.单例模式主要解决了什么问题?

  • 资源利用率:对于那些需要频繁创建和销毁,且消耗系统大量资源的类,如数据库连接、线程池、全局缓存等,使用单例模式可以确保在整个应用中只存在一个实例,从而减少系统开销并提高资源利用效率
  • 一致性保证:当多个客户端共享同一份全局配置信息或状态时,通过单例模式可以确保所有对象都访问同一份数据源,避免因多次实例化导致的数据不一致问题
  • 控制共享访问:对于一些全局唯一的操作对象,比如日志记录器、打印机服务等,需要确保在任何时刻只有一个实例来处理请求。单例模式能够防止对这类资源的并发访问冲突
  • 类初始化时机:有些情况下,我们希望某个类在第一次被引用时才初始化(懒加载),而不是一加载类就进行初始化,这样可以延迟初始化成本,并在实际需要的时候再创建实例
  • 多线程安全:在多线程环境下,如果不对单例的创建过程进行适当的同步控制,可能会导致多个实例的产生。单例模式的一个重要任务是提供一种线程安全的方式来创建和管理单个实例

3.单例模式的结构

  • 单例类。只能创建一个实例的类
  • 访问类。使用单例类

4.单例设计模式分类两种

  • 饿汉式:类加载就会导致该单实例对象被创建
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

2.单例模式的代码

1.饿汉式

1.饿汉式-方式1(静态变量方式)

java 复制代码
public class Singleton {

    // 私有构造方法
    public Singleton() {}

    // 静态变量创建类的对象
    private static Singleton instance = new Singleton();

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        // 静态变量创建类的对象
        return instance;
    }
}

说明:该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费


2.饿汉式-方式2(静态代码块方式)

java 复制代码
public class Singleton {

    // 私有构造方法
    private Singleton() {}

    // 在成员位置创建该类的对象
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

说明:该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题


2.懒汉式

1.懒汉式-方式1(线程不安全)

java 复制代码
public class Singleton {
    // 私有构造方法
    private Singleton() {}

    // 在成员位置创建该类的对象
    private static Singleton instance;

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

说明:从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题


2.懒汉式-方式2(线程安全)

java 复制代码
public class Singleton {
    // 私有构造方法
    private Singleton() {}

    // 在成员位置创建该类的对象
    private static Singleton instance;

    // 对外提供静态方法获取该对象
    public static synchronized Singleton getInstance() {

        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

说明:该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了


3.懒汉式-方式3(双重检查锁)

java 复制代码
public class Singleton {

    // 私有构造方法
    private Singleton() {}

    private static volatile Singleton instance;

   // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
		// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
        if(instance == null) {
            synchronized (Singleton.class) {
                // 抢到锁之后再次判断是否为空
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4.懒汉式-方式4(静态内部类方式)

java 复制代码
public class Singleton {

    // 私有构造方法
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

说明:第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性

小结:静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费


3.存在的问题与解决方式

破坏单例模式:使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射

1.序列化反序列化的破坏

1.来一个单例模式

java 复制代码
package com.andy.bug.demo01;

import java.io.Serializable;

public class Singleton implements Serializable {

    // 私有构造方法
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
    private Object readResolve() {
        return SingletonHolder.instance;
    }
}

2.编写一个测试类

java 复制代码
public class Test {
    public static void main(String[] args) throws Exception {
        // 往文件中写对象
        // writeObject2File();
        // 从文件中读取对象
        Singleton s1 = readObjectFromFile();
        Singleton s2 = readObjectFromFile();

        // 判断两个反序列化后的对象是否是同一个对象
        System.out.println(s1 == s2);
    }

    private static Singleton readObjectFromFile() throws Exception {
        // 创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/Users/andy/a.txt"));
        // 第一个读取Singleton对象
        Singleton instance = (Singleton) ois.readObject();

        return instance;
    }

    public static void writeObject2File() throws Exception {
        // 获取Singleton类的对象
        Singleton instance = Singleton.getInstance();
        // 创建对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/Users/andy/a.txt"));
        // 将instance对象写出到文件中
        oos.writeObject(instance);
    }
}

运行结果是false,表明序列化和反序列化已经破坏了单例设计模式


3.解决方式

在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象

java 复制代码
public class Singleton implements Serializable {

    // 私有构造方法
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    /**
     * 下面是为了解决序列化反序列化破解单例模式
     */
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

2.反射的破坏

1.来一个单例模式

java 复制代码
public class Singleton {

    // 私有构造方法
    private Singleton() {}
    
    private static volatile Singleton instance;

    // 对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

2.测试类

java 复制代码
public class Test {
    public static void main(String[] args) throws Exception {
    
        // 获取Singleton类的字节码对象
        Class clazz = Singleton.class;
        
        // 获取Singleton类的私有无参构造方法对象
        Constructor constructor = clazz.getDeclaredConstructor();
        
        // 取消访问检查
        constructor.setAccessible(true);

        // 创建Singleton类的对象s1
        Singleton s1 = (Singleton) constructor.newInstance();
        
        // 创建Singleton类的对象s2
        Singleton s2 = (Singleton) constructor.newInstance();

        // 判断通过反射创建的两个Singleton对象是否是同一个对象
        System.out.println(s1 == s2);
    }
}

运行结果是false,表明序列化和反序列化已经破坏了单例设计模式


3.解决方法

java 复制代码
public class Singleton {

    //私有构造方法
    private Singleton() {
        /*
           反射破解单例模式需要添加的代码
        */
        if(instance != null) {
            throw new RuntimeException();
        }
    }
    
    private static volatile Singleton instance;

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {

        if(instance != null) {
            return instance;
        }

        synchronized (Singleton.class) {
            if(instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}
相关推荐
捕鲸叉4 小时前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
小小小妮子~4 小时前
框架专题:设计模式
设计模式·框架
先睡4 小时前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
Damon_X12 小时前
桥接模式(Bridge Pattern)
设计模式·桥接模式
越甲八千17 小时前
重温设计模式--享元模式
设计模式·享元模式
码农爱java18 小时前
设计模式--抽象工厂模式【创建型模式】
java·设计模式·面试·抽象工厂模式·原理·23种设计模式·java 设计模式
越甲八千18 小时前
重温设计模式--中介者模式
windows·设计模式·中介者模式
犬余19 小时前
设计模式之桥接模式:抽象与实现之间的分离艺术
笔记·学习·设计模式·桥接模式
Theodore_102220 小时前
1 软件工程——概述
java·开发语言·算法·设计模式·java-ee·软件工程·个人开发
越甲八千21 小时前
重拾设计模式--组合模式
设计模式·组合模式