zoukankan      html  css  js  c++  java
  • Spring中常见的设计模式——单例模式

    一、单例模式的应用场景

      单例模式(singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。J2EE中的ServletContext,ServletContextConfig等;Spring中的ApplicationContext、数据库连接池等。

    二、饿汉式单例模式

      饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。它是绝对的线程安全、在线程还没出现以前就实现了,不可能存在访问安全问题。

      优点:没有增加任何锁,执行效率高,用户体验比懒汉式好。

      缺点:类加载的时候就初始化了,用不用都进行,浪费内存。

      Spring 中IoC容器ApplocationContext本身就是典型的饿汉式单例模式:

    public class HungrySingleton {
        private static final HungrySingleton h = new HungrySingleton();
    
        private HungrySingleton() {
        }
    
        public static HungrySingleton getInstance() {
            return h;
        }
    }

      饿汉式单例模式适用于单例对象较少的情况。

    三、懒汉式单例模式

      被外部调用才会加载:

    public class LazySimpleSingleton {
        private LazySimpleSingleton() {
        }
    
        private static LazySimpleSingleton lazy = null;
    
        public static LazySimpleSingleton getInstance() {
            if (lazy == null) {
                lazy = new LazySimpleSingleton();
            }
            return lazy;
        }
    }

    利用线程创建实例:

    public class ExectorThread implements Runnable {
        @Override
        public void run() {
            LazySimpleSingleton simpleSingleton = LazySimpleSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + ":" + simpleSingleton);
        }
    }

    客户端代码:

    public class LazySimpleSingletonTest {
        public static void main(String[] args) {
            Thread t1 = new Thread(new ExectorThread());
            Thread t2 = new Thread(new ExectorThread());
            t1.start();
            t2.start();
            System.out.println("END");
        }
    }

    结果:

    END
    Thread-1:singleton.Lazy.LazySimpleSingleton@298c37fd
    Thread-0:singleton.Lazy.LazySimpleSingleton@6ebc1cfd

    可以看到 产生的两个实例的内存地址不同说明产生了两个实例,大家可以通过以下打断点的方式实现不同Thread运行状态见进行切换。

      要解决线程问题第一反应是加 synchronized 加在创建实例的地方:public static synchronized LazySimpleSingleton getInstance(),但当线程数量较多时,用Synchronized加锁,会使大量线程阻塞,就需要更好的解决办法:

        public static LazySimpleSingleton getInstance() {
            if (lazy == null) {
                synchronized (LazySimpleSingleton.class) {
                    if (lazy == null) {
                        lazy = new LazySimpleSingleton();
                    }
                }
            }
            return lazy;
        }

      synchronized (lock) lock这个对象就是 “锁”,当两个并行的线程a,b,当a先进入同步块,即a先拿到lock对象,这时候a就相当于用一把锁把synchronized里面的代码锁住了,现在只有a才能执行这块代码,而b就只能等待a用完了lock对象锁之后才能进入同步块。但是用到 synchronized 总归是要上锁的,对性能还是有影响,那就用这种方式:用内部类的方式进行懒加载。

    public class LazyInnerClassSingleton {
        private LazyInnerClassSingleton() {
        }
    
        private static final LazyInnerClassSingleton getIngestance() {
            return LazyHolder.LAZY;
        }
    
        private static class LazyHolder {
            private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
        }
    }

      内部类在LazyInnerClassSingleton类加载时加载,解决了饿汉式的性能问题,LazyInnerClassSingleton在内部类加载时,getIngestance()方法被调用之前实例化,解决了线程不安全问题。

     四、反射破坏单例

    public class LazyInnerClassSingletonTest {
        public static void main(String[] args) {
            try {
                Class<?> clazz = LazyInnerClassSingleton.class;
                //通过反射回去私有构造方法
                Constructor constructor = clazz.getDeclaredConstructor(null);
                //强制访问
                constructor.setAccessible(true);
                //暴力初始化
                Object o1 = constructor.newInstance();
                //创建两个实例
                Object o2 = constructor.newInstance();
                System.out.println("o1:" + o1);
                System.out.println("o2:" + o2);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    结果:

    o1:singleton.Lazy.LazyInnerClassSingleton@1b6d3586
    o2:singleton.Lazy.LazyInnerClassSingleton@4554617c

    创建了两个实例,违反了单例,现在在构造方法中做一些限制,使得多次重复创建时,抛出异常:

     private LazyInnerClassSingleton() {
            if (LazyHolder.class != null) {
                throw new RuntimeException("不允许创建多个实例");
            }
        }

    这应该就是最好的单例了,哈哈哈。

    五、注册式单例模式

      注册式单例模式又称为登记式单例模式,就是将每个实例都登记到某个地方,使用唯一标识获取实例。注册式单例模式有两种:枚举式单例模式、容器式单例模式。注册式单例模式主要解决通过反序列化破坏单例模式的情况。

    1.枚举式单例模式 

    public enum EnumSingleton {
        INSTANCE;
        private Object data;
    
        public Object getData() {
            return data;
        }
    
        public void steData(Object data) {
            this.data = data;
        }
    
        public static EnumSingleton getInstance() {
            return INSTANCE;
        }
    }

    测试代码:

    public class EnumSingletonTest {
        public static void main(String[] args) {
            try {
                EnumSingleton instance1 = EnumSingleton.getInstance();
                EnumSingleton instance2 = null;
                instance1.steData(new Object());
    
                FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(instance1);
                oos.flush();
                oos.close();
    
                FileInputStream fis = new FileInputStream("EnumSingleton.obj");
                ObjectInputStream ois = new ObjectInputStream(fis);
                instance2 = (EnumSingleton) ois.readObject();
                ois.close();
    
                System.out.println(instance1.getData());
                System.out.println(instance2.getData());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    结果:

    java.lang.Object@568db2f2
    java.lang.Object@568db2f2

      那枚举式单例是如何解决反序列化得问题呢?

      通过反编译,可以在EnumSingleton.jad文件中发现static{} 代码块,枚举式单例模式在静态代码块中给INSTANCE进行了赋值,是饿汉式单例模式的实现。查看JDK源码可知,枚举类型其实通过类名和类对象找到一个唯一的枚举对象。因此,枚举对象不可能被类加载器加载多次。

      当你试图用反射破坏单例时,会报 Cannot reflectively create enum objects ,即不能用反射来创建枚举类型。进入Customer的newInstance(),其中有判断:如果修饰符是Modifier.ENUM,则直接抛出异常。JDK枚举的语法特殊性及反射也为美剧保驾护航,让枚举式单例模式成为一种比较优雅的实现。

    2.容器式单例

    public class ContainerSingleton {
        private ContainerSingleton() {
        }
    
        private static Map<String, Object> ioc = new ConcurrentHashMap<>();
    
        public static Object getBean(String className) {
            synchronized (ioc) {
                if (!ioc.containsKey(className)) {
                    Object o = null;
                    try {
                        o = Class.forName(className).newInstance();
                        ioc.put(className, o);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return o;
                } else {
                    return ioc.get(className);
                }
            }
        }
    }

    spring中使用的就是容器式单例模式。

      

      

  • 相关阅读:
    [禅悟人生]学习是一种偏执
    [家里蹲大学数学杂志]第393期中山大学2015年计算数学综合考试考博试题回忆版
    [家里蹲大学数学杂志]第392期中山大学2015年泛函分析考博试题回忆版
    [数分提高]2014-2015-2第2教学周第1次课
    [数分提高]2014-2015-2第1教学周第2次课
    [数分提高]2014-2015-2第1教学周第1次课
    所教课程
    16种床上动作的内涵图,你都看懂了吗?
    为什么美国学生学的数学比我们简单却还能做出很牛逼的东西?
    来自数学君的羊年祝福
  • 原文地址:https://www.cnblogs.com/xcgShare/p/11951891.html
Copyright © 2011-2022 走看看