首页 > 其他分享 >单例模式的运用

单例模式的运用

时间:2023-06-03 11:57:06浏览次数:27  
标签:Singleton getInstance 静态 模式 对象 static 单例 运用 public

目录

一、介绍

单例模式:属于创建型模式,涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例设计模式分类两种:

  • 饿汉式:类加载就会导致该单实例对象被创建。

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

二、饿汉式

2.1 静态变量方式

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

/**
 * 静态变量创建类的对象
 */
public class Singleton {
    // 私有类的无参构造
    private Singleton() {}
    // 给本类创建一个新的对象,并用私有化无法访问,使用静态关键字static来修饰
    private static Singleton instance = new Singleton();
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance(){
        return instance;
    }
}

public static void main(String[] args) {
          // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

2.2 静态代码块方式

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

/**
 * 静态变量创建类的对象
 */
public class Singleton {
    // 私有的构造方法
    private Singleton(){}
    // 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰
    private static Singleton instance;//null
    // 在静态代码块中进行赋值,创建Singleton的对象
    static{
        instance = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
        return instance;
    }
}

public static void main(String[] args) {
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

2.3 枚举方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会加载一次,枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/*
* 枚举方式
* 枚举方式属于恶汉式方式
**/
public enum Singleton {
    INSTANCE;
}

public static void main(String[] args) {
    // 重复调用Singletion枚举类的INSTANCE
    Singleton instance1 = Singleton.INSTANCE;
    Singleton instance2 = Singleton.INSTANCE;
    System.out.println(instance1 == instance2);
}

三、懒汉式

3.1 线程不安全方式

该方式在成员位置声明Singleton类型的静态变量,当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,在多线程环境,出现线程安全问题

/**
 * 线程不安全
 */ 
public class Singleton {
    // 私有的构造方法
    private Singleton(){}
    // 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰
    private static Singleton instance;//null
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
        // 判断instance是否为null,如果为null,说明还没有创建Singleton类的对象
        // 如果不为空,说明已将创建过,返回对象即可
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

public static void main(String[] args) {
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

3.2 线程安全方式

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

/**
 * 线程安全
 */ 
public class Singleton {
    // 私有的构造方法
    private Singleton(){}
    // 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰
    private static Singleton instance;//null
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static synchronized Singleton getInstance() {
        // 判断instance是否为null,如果为null,说明还没有创建Singleton类的对象
        // 如果不为空,说明已将创建过,返回对象即可
        if(instance == null){
            // 加锁前,线程1、2都进入到该位置,此时就会出现创建多个对象的情况
            // 加锁后,线程1进入,线程2会在外面等待线程1运行完毕,释放锁后才继续执行
            instance = new Singleton();
        }
        return instance;
    }
}

public static void main(String[] args) {
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

3.3 双重检查锁方式

该方式对懒汉模式中加锁的问题做出变化,对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机

多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性

/*
* 双重检查锁
* 添加 volatile关键字之后的双重检查锁模式是一种比较好的单例
* 实现模式,能够保证在多线程的情况下线程安全也不会有性能问题
**/
public class Singleton {
    // 私有的构造方法
    private Singleton(){}
    // 给本类声明Singleton类型的变量,并用私有化无法访问,使用静态关键字static来修饰
    private static volatile Singleton instance;//null
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
        // 第一次判断,判断instance是否为null,如果为null,说明还没有创建Singleton类的对象,不需要抢占锁
        // 如果不为空,说明已将创建过,返回对象即可,读操作直接返回,没有加锁
        if(instance == null){
            // 第二次判断,写操作需要加锁
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

public static void main(String[] args) {
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

3.4 静态内部类方式

该方式的实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序

静态内部类方式在没有加任何锁的情况下,保证线程安全,并且没有任何性能影响和空间的浪费

/*
* 静态内部类方式
* 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
**/
public class Singleton {
    // 私有的构造方法
    private Singleton(){}
    //定义一个静态内部类
    private static class SingletonHolder {
        //在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
        // 内部类直接调用对象
        return SingletonHolder.INSTANCE;
    }
}

public static void main(String[] args) {
        // 重复调用Singletion类的getInstance方法
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        // 判断获取的两个对象是否为同一个对象,即申请的内存地址是否相同,来证明单例模式
        System.out.println(instance1 == instance2);//true
}

四、破坏单例模式

4.1 序列化破坏

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

对象里面的值是一样的,只不过对象的引用不一样,相当于是两个对象,涉及到了深拷贝

/**
 * 静态内部类方式的单列模式
 */
public class Singleton implements Serializable {
    // 私有的构造方法
    private Singleton(){}
    //定义一个静态内部类
    private static class SingletonHolder {
        //在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
        // 内部类直接调用对象
        return SingletonHolder.INSTANCE;
    }
}

public class Client {
    public static void main(String[] args) throws Exception {
//        writeObjectToFile();
        Singleton s1 = readObjectFromFile();
        Singleton s2 = readObjectFromFile();
        System.out.println(s1 == s2);
    }
    // 使用序列化向文件中写数据(对象)
    private static void writeObjectToFile() throws IOException {
        // 获取Singleton对象
        Singleton instance = Singleton.getInstance();
        // 创建对象输出流对象
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
        // 将对象写入
        objectOutputStream.writeObject(instance);
        // 释放资源
        objectOutputStream.close();
    }
    // 使用反序列化读取文件的数据(对象)
    private static Singleton readObjectFromFile() throws Exception {
        // 创建对象输入流对象
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
        // 读取对象
        Singleton instance = (Singleton) objectInputStream.readObject();
        System.out.println(instance);
        // 释放资源
        objectInputStream.close();
        return instance;
    }
}

4.2 序列化破坏解决办法

想要解决序列化、反序列化对单例模式的破坏,我们需要在Singleton类中添加readResolve()方法,在其被反序列化时调用,返回这个方法的值(对象)。 原理是在输入流对象的readObject方法的底层实现中,如果发现输入的是对象类型, 则会调用readOrdinaryObject方法,该方法中会自动执行hasReadResolveMethod方法,来判断类中是否有readResolve方法,如果有,则会去执行invokeReadResolve方法来获取类中专门提供的实例,从而解决反序列化破坏单例模式的问题。

// 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
public Object readResolve(){
    return SingletonHolder.INSTANCE;
}

4.3 反射破坏

代码运行结果是false,表明反射已经破坏了单例设计模式。

/**
 * 静态内部类方式的单列模式
 */
public class Singleton implements Serializable {
    // 私有的构造方法
    private Singleton(){}
    //定义一个静态内部类
    private static class SingletonHolder {
        //在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
        // 内部类直接调用对象
        return SingletonHolder.INSTANCE;
    }
}

public class Client {
    public static void main(String[] args) throws Exception {
        // 获取Singleton字节码对象
        Class<Singleton> singletonClass = Singleton.class;
        // 获取无参构造方法对象
        Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();
        // 取消访问检查
        declaredConstructor.setAccessible(true);
        // 创建Singleton对象
        Singleton s1 = (Singleton) declaredConstructor.newInstance();
        Singleton s2 = (Singleton) declaredConstructor.newInstance();
        // 结果返回false,说明反射破坏了单例模式
        System.out.println(s1 == s2);
    }
}

4.4 反射破坏解决办法

当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此次操作。

/**
 * 静态内部类方式的单列模式
 */
public class Singleton {
    // 定义一个flag判断是否已经创建过对象
    private static boolean flag = false;
    // 给私有构造方法添加锁,预防线程安全问题
    private Singleton(){
        synchronized(Singleton.class){
            // flag为false则跳过,直接正常创建,若flag为true,说明已经创建过,抛出异常
            if(flag){
                throw new RuntimeException("不能创建多个对象");
            }
            // 第一次创建后,应该将flag设置为true
            flag = true;
        }
    }
    //定义一个静态内部类
    private static class SingletonHolder {
        //在内部类中声明Singleton类型的变量并初始化外部类的对象,并用私有化无法访问,使用静态关键字static和final来修饰
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供一个公共的访问方式,由于外界无法创建Singleton对象,无法调用非静态的方法,所以设计为静态的方法
    public static Singleton getInstance() {
        // 内部类直接调用对象
        return SingletonHolder.INSTANCE;
    }
}

记录每一个学习瞬间

标签:Singleton,getInstance,静态,模式,对象,static,单例,运用,public
From: https://www.cnblogs.com/MrDevil-k/p/17453751.html

相关文章

  • android-夜间模式
    资源1AndroidMaterialDesign系列之夜间模式阐述了夜间模式的资源文件,告知建立了values-night文件夹对于夜间模式的颜色和主题配置,我们需要建立一个res下建立一个values-night文件夹,里面放着夜间主题样式的color等资源。colors.xml配置如下:<?xmlversion="1.0"encoding="utf-8"......
  • 移动开发之设计模式-组合模式(IOS&Android)
    组合模式组合模式(CompositePattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。这种模式创建了一个包含自己对象组的类。该类提供了修改相......
  • java单例模式几种实现方式
    1、饿汉式(线程安全,调用效率高,但是不能延时加载):publicclassImageLoader{privatestaticImageLoaderinstance=newImageLoader;privateImageLoader(){}publicstaticImageLoadergetInstance(){returninstance;}}一上来就把单例对象创建出来了,要用的时候直......
  • Sentinel规则Pull模式持久化
    阅读文本大概需要3分钟。   前一篇【使用Nacos存储Sentinel的限流规则】讲了基于Nacos的Push模式持久化,这里讲下基于本地文件的Pull模式持久化。在网上看到一篇讲这个讲得不错的:从官网的说明https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel#Pull模式有......
  • Golang - 选项模式 vs 构建器模式
    在使用Golang创建复杂对象时,常用的两种模式是选项模式(Optionspattern)和构建器模式(Builderpattern)。这两种模式各有优缺点,选择适合项目需求的模式取决于具体情况。问题假设我们想创建一个具有许多可选参数的复杂对象。一种方法是创建一个构造函数,该构造函数接受所有参数,并为可......
  • 方芳:坡面尺度上混合植被恢复模式对土壤修复的影响
    武汉市江夏区交通局武汉市江夏区公路局  武汉市江夏区公路建筑工程公司武汉市江夏城投集团有限公司武汉江夏路桥工程总公司 武汉工程大学 土木工程与建筑学院    方芳    15927602711坡面尺度上混合植被恢复模式对土壤修复的影响摘要:随着人类活动......
  • 工厂模式配置servlet(servlet升级版)
    1、创建一个类点击查看代码packagecom.bh.controller;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;impo......
  • 如何将python运用于实践
    看过很多本Python教程,敲过很多python例子代码,试过很多个python项目但是心中还是很堵,没有畅通的感觉就像一个闯入丛林深处的初级猎人不知道哪里有猎物,有什么猎物,猎物有什么习性,如何有效布置陷阱,需要带多少食物方向如何确定,如何应对蚊虫叮咬,如何应对毒蛇。。。一点都不懂,一窍都......
  • 设计模式
    设计模式介绍设计模式是程序员在面对同类软件工程设计问题所总结出来的有用经验,是某类问题的通用解决方案。作用:使程序(软件)具有更好:代码重用性(即相同功能的代码,不用多次编写)可读性(即编程规范性,便于其他程序员的阅读和理解)可扩展性(即:当需要增加新的功能时,非常的方便,称为可维......
  • 商业模式:收入、价值与过程
    如上,只说了求橙商学院,商业模式课内的一个小练习,这篇稍稍展开到浙大的郭斌教授第一个半天的核心内容。商业的基础是市场经济,市场经济的基本行为是交易,交易的本质是价值交换,所以,我们可以用如下一句话来概括所有的商业模式:为谁提供什么价值,靠什么方式转换为收入,前两者之间做什么商业过......