zoukankan      html  css  js  c++  java
  • 单例模式

    单例模式

    1.饿汉模式

    类加载的时候,就进行对象的创建,系统开销较大,但是不存在线程安全

    * 饿汉模式-单例对象构建方法
    * (类加载的时候,就进行对象的创建,系统开销较大,影响性能,所以多数采用饿汉模式,在使用时才真正的创建单例对象)
    方式一:饿汉模式:性能最差
    class Singleton1 {
    	private static Singleton1 singleton = new Singleton1(); // 建立对象
    	
    问题:每次类加载的时候,就进行对象的创建,性能开销大,是一种空间换时间的方式
    


    2.懒汉模式

    多数采用饿汉模式,在使用时才真正的创建单例对象,但是存在线程安全

    懒汉模式

    线程不安全

    方式二:懒汉模式-性能不安全
    class Singleton2 {
    	private static Singleton2 singleton = null; // 不建立对象
    /*synchronized 可以解决线程安全问题,但是存在性能问题,即使singleton!=null也需要先获得锁*/
    public synchronized static Singleton2 getInstance() {
    	if (singleton == null) { // 先判断是否为空
    		try {
    			Thread.sleep(1000);
    			System.out.println("构建这个对象可能耗时很长...");
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		singleton = new Singleton2(); // 懒汉式做法
    	}
    	return singleton;
    }
    是一种时间换空间的方式
    

    问题:每次线程都要走同步代码块性能 不高


    线程安全doublecheck

    方式三:懒汉模式-性能安全
    class Singleton3 {
    	private static Singleton3 singleton = null; // 不建立对象
    	private Singleton3() {
    	}
    	/*synchronized 代码块进行双重检查,可以提高性能*/
    	public static Singleton3 getInstance() {
    		if (singleton == null) { // 先判断是否为空
    			synchronized (Singleton3.class) {
    				//此处必须进行双重判断,否则依然存在线程安全问题
    				if(singleton == null){
    					try {
    						Thread.sleep(1000);
    						System.out.println("构建这个对象可能耗时很长...");
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					singleton = new Singleton3(); // 懒汉式做法
    				}
    			}
    		}
    		return singleton;
    	}
    
    这里的synchronize只有开头几个线程会进来,此时singleton为null。
    为什么要进行双重判断?
    因为同步代码块的存在,多个线程程序刚启动的时候会一起进来,1个线程进入代码块执行方法,结束后,其他线程执行方法前要判断这个对象是否为null,这里的判断是判断是否一初始化实例。
    第一次是判断单例是否存在,准备使用
    第二次是判断单例是否存在,准备创建
    

    3.静态内部类单例

    兼具懒汉模式和饿汉模式的优点

    序列化方式和反射方式都可以破坏单例

    定义饿汉类

    public class HungrySingleton implements Serializable,Cloneable{
    
        private final static HungrySingleton hungrySingleton;
    
        static{
            hungrySingleton = new HungrySingleton();
        }
        private HungrySingleton(){
            if(hungrySingleton != null){
                throw new RuntimeException("单例构造器禁止反射调用");
            }
        }
        public static HungrySingleton getInstance(){
            return hungrySingleton;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return getInstance();
        }
    }
    

    1.序列化破坏单例

    序列化的时候对于普通对象会new Instance

    HungrySingleton instance = HungrySingleton.getInstance();
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
    oos.writeObject(instance);
    File file = new File("singleton_file");
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    HungrySingleton newInstance = (HungrySingleton) ois.readObject();
    System.out.println(instance);
    System.out.println(newInstance);
    System.out.println(instance == newInstance);
    

    控制台输出:

    image-20220115083837125

    发现输出的是不同的地址,观察ois.readObject();的源码发现

    因为对象是object的,所以走到

    case TC_OBJECT:
        if (type == String.class) {
            throw new ClassCastException("Cannot cast an object to java.lang.String");
        }
        return checkResolve(readOrdinaryObject(unshared));
    

    在这个readOrdinaryObject中发现代码片

    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
    

    观察isInstantiable()方法,说明返回的一定是true

    /**
     * 如果表示的类是可序列化的,并且可以由序列化运行时实例化,则返回 true
     * Returns true if represented class is serializable/externalizable and can
     * be instantiated by the serialization runtime--i.e., if it is
     * externalizable and defines a public no-arg constructor, or if it is
     * non-externalizable and its first non-serializable superclass defines an
     * accessible no-arg constructor.  Otherwise, returns false.
     */
    boolean isInstantiable() {
        requireInitialized();
        return (cons != null);
    }
    

    所以调用desc.newInstance(),这里就新建了一个实例,所以和原来的实例对象是不同的


    拓展:序列化下实现单例

    但观察源码,可以发现,如果object拥有readResolve()方法,则最后会返回这个方法的返回对象覆盖原来new Instance()对象,所以加上这个方法试试

    修改HungrySingleton,在readResolve()方法中返回单例对象

    public class HungrySingleton implements Serializable,Cloneable{
    
        private final static HungrySingleton hungrySingleton;
    
        static{
            hungrySingleton = new HungrySingleton();
        }
        private HungrySingleton(){
            if(hungrySingleton != null){
                throw new RuntimeException("单例构造器禁止反射调用");
            }
        }
        public static HungrySingleton getInstance(){
            return hungrySingleton;
        }
    
        private Object readResolve(){
            return hungrySingleton;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return getInstance();
        }
    }
    

    调试可得,发现输出的的确是单例

    image-20220115084632925


    2.反射方式破坏单例

    懒汉可以采用防御机制,在无参构造方法中防御,但是懒汉模式无法防御

    Class objectClass = LazySingleton.class;
    Constructor constructor = objectClass.getDeclaredConstructor();
    constructor.setAccessible(true);
    LazySingleton newInstance = (LazySingleton) constructor.newInstance();
    LazySingleton instance = LazySingleton.getInstance();
    System.out.println(instance);
    System.out.println(newInstance);
    

    输出

    image-20220115085439139

    但是饿汉模式可以在无参构造器中加上防御机制

    private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    
    Class objectClass = HungrySingleton.class;
    Constructor constructor = objectClass.getDeclaredConstructor();
    constructor.setAccessible(true);
    HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
    HungrySingleton instance = HungrySingleton.getInstance();
    System.out.println(instance);
    System.out.println(newInstance);
    

    image-20220115085551496


    3.使用枚举方式

    针对序列化:

    序列化的对象是确定的枚举值,所以一定是相同的

    针对反射:

    enum没有无参构造器

    反射无法使用枚举!

    通过反编译查看enum源码发现:

    image-20220114233722286


    4.匹配源码

    饿汉模式:

    image-20220114235229650


    容器单例思想

    image-20220114235413729

    image-20220114235652007

  • 相关阅读:
    使用sstream来进行类型转换
    C++中的istringstream
    C++中如何按照map中的value来进行排序
    019:别叫,这个大整数已经很简化了!
    ccf题库20170903--Json查询
    ccf题库中2015年12月2号消除类游戏
    ccf题库中2016年4月2日俄罗斯方块问题
    C++基础算法学习——逆波兰表达式问题
    C++基础算法学习——N皇后问题
    C++基础算法学习——汉洛塔问题
  • 原文地址:https://www.cnblogs.com/yslu/p/15806101.html
Copyright © 2011-2022 走看看