欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

单例模式学习笔记与总结

程序员文章站 2022-03-26 15:54:05
系列文章目录提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加例如:第一章 Python 机器学习入门之pandas的使用提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录系列文章目录前言一、什么是单例模式?二、代码展示前言Spring容器中对象默认是单例的,而且开发中经常会用到单例模式,比如:1、整合多个模块的log时候,通常会将log对象设计成单例模式,便于查看log与分析2、多个模块读取同一个配置文件时,将配置文件对象设计成单例模式…一、什...


前言

Spring容器中对象默认是单例的,而且开发中经常会用到单例模式,比如:
1、整合多个模块的log时候,通常会将log对象设计成单例模式,便于查看log与分析
2、多个模块读取同一个配置文件时,将配置文件对象设计成单例模式

一、什么是单例模式?

数学与逻辑学中,singleton定义为“有且仅有一个元素的集合”。
单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
摘自百度经验.

						自己总结:单例模式就是一个类仅有一个实例,适用于单线程,相对于多例模式可以节省内存并且占用系统资源少,不让用new关键字去获取对象,但是单例模式中没有抽象类,所以会导致单例模式中的类的拓展性不强。

二、代码展示

饿汉式单例模式

package com.rong.singleton;

/**
 * @Author: RONG
 * @Date: 2021/1/4 21:46
 */
    /*饿汉式单例*/
public class Hungry {
    /*首先私有化构造器*/
    private Hungry(){

    }
    /*在类被加载时就会实例化一个对象*/
    private final static Hungry hungry = new Hungry();

    /*获取实例化对象的方法*/
    public static Hungry getInstance(){
        return hungry;
    }

    /*
    * 饿汉式单例可以简单有快速的实例化对象,final static可以保证线程是安全的(只有在类加载时才会对对象进行初始化)
    * 但是,无论是否需要此实例化对象,它都会在类加载时将对象实例化,从而占用了资源。
    * */

}

懒汉式单例模式

线程不安全的懒汉式

package com.rong.singleton;

/**
 * @Author: RONG
 * @Date: 2021/1/4 21:54
 */
public class LazyMan {
    /*私有化构造器*/
    private LazyMan(){}

    /*懒汉式模式只有在调用getInstanceOf()方法的时候才会创建对象*/
    private static LazyMan lazyMan;

    public static LazyMan getInstance(){
        /*先判断lazyMan对象是否存在,如果存在则直接返回,不存在创建一个*/
        if(lazyMan==null){
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }

    /*
    * 这样的懒汉式在多线程并发下会创建多个对象
    * */
}

双重检测锁懒汉式(DCL懒汉式)

package com.rong.singleton;

/**
 * @Author: RONG
 * @Date: 2021/1/4 21:54
 */
public class LazyMan {
    /*私有化构造器*/
    private LazyMan(){}

    /*懒汉式模式只有在调用getInstanceOf()方法的时候才会创建对象*/
    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance(){
        /*第一次判断lazyMan对象是否存在,如果不存在则给LazyMan这个对象添加同步锁,这样可以保证线程安全*/
        if(lazyMan==null){
            synchronized(LazyMan.class){   
                /*再次判断lazyMan对象是否存在,如果存在则直接返回,不存在创建一个*/
                if(lazyMan==null){
                    lazyMan=new LazyMan();//不是原子性操作
                }
            }
        }
     
        return lazyMan;
    }
    /*
    * 双重检测锁的懒汉式单例(DCL懒汉式)虽然在多线程下只能实例化一个对象,但是有可能会出现返回的对象是还没完成构造的对象
    * 因为new LazyMan的过程看似是一步,其实在JVM中执行了多步
    *
    * 首先JVM会根据new后面的参数,在常量池中定位这个类的引用符号
    * 如果没有找到则说明类没有被加载,开始对类进行加载并解析和初始化
    * 虚拟机再为对象分配内存
    * 再将分配的内存中的值进行初始化为0值
    * 最后再调用对象的init方法
    *
    * 当在一个线程在执行这些步骤过程时,此时恰好其他线程来调用getInstanceOf方法,则有可能打乱JVM执行这些步骤的顺序,从而导致线程不安全
    * 所以要加上volatile保证原子性
    * */

}

静态内部类单例模式

package com.rong.singleton;

/**
 * @Author: RONG
 * @Date: 2021/1/4 23:22
 */
public class OuterClass {

    private OuterClass(){}

    public static OuterClass getInstance(){
        return InnerClass.Instance;
    }

    private static class InnerClass{
        public static OuterClass Instance = new OuterClass();
    }

    /*
    * 外部类在加载时不需要立即加载内部类,内部类只有在调用getInstance方法时才会加载,并且在getInstance方法在被调用之后,
    * 虚拟机会将Instance变量进行初始化,将内部类加载进内存,这样既保证了线程的安全性,又保证了单例的唯一性
    *
    * 然而java中存在反射机制,反射可以破坏这些单例结构
    * 并且静态内部类无法传参
    * */
}

反射破坏单例

package com.rong.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @Author: RONG
 * @Date: 2021/1/4 21:54
 */
public class LazyMan {
    /*私有化构造器*/
    private LazyMan(){}

    /*懒汉式模式只有在调用getInstanceOf()方法的时候才会创建对象*/
    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance(){
        /*第一次判断lazyMan对象是否存在,如果不存在则给LazyMan这个对象添加同步锁,这样可以保证线程安全*/
        if(lazyMan==null){
            synchronized(LazyMan.class){
                /*再次判断lazyMan对象是否存在,如果存在则直接返回,不存在创建一个*/
                if(lazyMan==null){
                    lazyMan=new LazyMan();
                }
            }
        }

        return lazyMan;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //用无参构造器实例化一个LazyMan对象
        LazyMan instance1 = new LazyMan();
        //利用反射来破坏构造器的私有化,并创建实例
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

运行结果

"C:\Program Files\Java\jdk1.8.0_191\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2019.3.4\lib\idea_rt.jar=7233:D:\IntelliJ IDEA 2019.3.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_191\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_191\jre\lib\rt.jar;D:\study\day04\target\classes" com.rong.singleton.LazyMan
com.rong.singleton.LazyMan@1b6d3586
com.rong.singleton.LazyMan@4554617c

Process finished with exit code 0

反射破坏了构造器的私有后,也破坏了单例

枚举单例

package com.rong.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @Author: RONG
 * @Date: 2021/1/5 10:41
 */
public enum EnumSingleton {
    INSTANCE;

    public EnumSingleton getInstance(){
        return INSTANCE;
    }
	//枚举类实例的创建是线程安全的,并且它只能拥有一个实例
}

Constructor.java源码中枚举类的处理

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
	//如果用反射来创建枚举类实例,则会报错无法用反射来创建枚举类实例
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;

测试

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingleton instance = EnumSingleton.INSTANCE;
        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingleton instance1 = declaredConstructor.newInstance();
        System.out.println(instance==instance1);
    }

}

结果

Exception in thread "main" java.lang.NoSuchMethodException: com.rong.singleton.EnumSingleton.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getConstructor(Class.java:1825)
	at com.rong.singleton.Test.main(EnumSingleton.java:22)

Process finished with exit code 1

显然测试结果的异常与构造器类中对枚举类的处理抛出的异常不一致,测试抛出的异常是没有这样的EnumSingleton的初始化方法,由此可知EnumSingleton的默认构造方法并不是无参构造。
这里我们通过查看枚举类Enum的源码可以得知,改类的默认构造方法如下:

protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    //有两个参数,一个String型,一个int型

重新测试

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingleton instance = EnumSingleton.INSTANCE;
        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingleton instance1 = declaredConstructor.newInstance();
        System.out.println(instance==instance1);
    }

}

结果

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.rong.singleton.Test.main(EnumSingleton.java:24)

此次结果跟猜想的一样

所以枚举类的构造器默认是带String与int型两个参数的构造器,并且枚举类不能用反射来实例化对象。

本文地址:https://blog.csdn.net/weixin_45490890/article/details/112203896