zoukankan      html  css  js  c++  java
  • Design Pattern [1] —— 单例模式 Singleton

    单例模式:一个类只能构造一个实例对象("构造器私有")

    场景:

      Windows任务管理器、回收站

      项目中,配置文件的类,一般只有一个对象

      网站的计数器、时钟

      数据库连接池

      Servlet

      Spring中的Bean(缓存中取bean很快,减少jvm垃圾回收)(当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象)

    好文:https://zhuanlan.zhihu.com/p/33102022  (本文还需要进一步根据面经深化)


     我的Github里有源码,可以clone下来自己跑下:https://github.com/Yang2199/Design-Pattern/tree/master/src

    1)饿汉式单例

      ==》上来直接new对象,所有类实例化。坏处是:大量浪费不必要的资源(因为很多类   不需要实例化)

    public class Hungry {
        private Hungry(){} //构造函数
        private final static Hungry HUNGRY = new Hungry();//直接实例化,new出对象
        public static Hungry getInstance(){
            return HUNGRY;
        }
    }

    2)懒汉式单例 

       ==》懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。

        线程不安全:多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,

        因此需要加锁解决线程同步问题( Synchronized 同步锁来修饰 getInstance 方法)

    public class Singleton {
        private Singleton() {}  //单例=》私有构造器
        private static Singleton instance = null;  //单例对象
        public static Singleton getInstance() {  //调用getInstance方法才会开始构造对象
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }

    但是这种方法不符合线程安全:

    public class Singleton {
        private Singleton() {
            System.out.println(Thread.currentThread().getName()); //构造时候打印线程名
        }  //私有构造函数
        private static Singleton instance = null;  //单例对象
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            for(int i =0;i<10;i++){
                new Thread(  ()->{Singleton.getInstance();}  ).start();  //1.启动新线程 2.lambda表达式
            }
        }
    }

    可能会出现构造了很多个的情形

    方法一:双重锁检测 DCL

      ==》首先将类加同步锁(syn),但是new语句不是原子操作,所以对了类的实例加volatile锁(可见性)

    第一把锁:synchronized锁

    public static Singleton getInstance() {
        if (instance == null) {     //外层判断
            synchronized (Singleton.class) {    //将Singleton类 加锁
                if (instance == null) {     //原有判断
                    instance = new Singleton();
              // 上面这行 不是原子操作:
              // 1.分配内存空间 2.执行构造方法,初始化对象 3.把这个对象指向这个空间 } } }
    return instance; }
    //加上两层:一层if、一层锁
    //这样就能保证单例在多线程下的唯一性
    //双重检查模式:DCL (Double Check)

    第二把锁:volatile锁

    private volatile static Singleton instance = null;  //由于instance = new Singleton()的非原子性(new对象3步),所以需要volatile保证强制同步一致

    volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值

    但是,还是可以用反射来破坏DCL:

        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            Singleton instance = Singleton.getInstance();
            Constructor<Singleton> declaredConstructor =Singleton.class.getDeclaredConstructor(null);  //通过反射获取declaredConstructor这个构造器
            declaredConstructor.setAccessible(true);
            Singleton instance2 = declaredConstructor.newInstance();
    
            System.out.println(instance.hashCode() );
            System.out.println(instance2.hashCode() );
        }

    实现单例模式,3种方法对比:


     方法二:用静态内部类

    public class Holder {
        private Holder(){}
    
        public static Holder getInstance() {return InnerClass.HOLDER;}
    
        private static class InnerClass{
            private static final Holder HOLDER = new Holder();
        }
    }

    这个方法也能被反射破坏单例的唯一性

    方法三:枚举

      ==》直接把类名前的class替换成enum就好了,因为枚举无法反射

    枚举:(通过查看源码可知,枚举时,无法反射。)

    public enum EnumSingleton { //这里是enum而不是class
        INSTANCE;
        public EnumSingleton getInstance(){
            return INSTANCE;
        }
    }

    测试:

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

    会显示:

    异常:枚举无法创造反射

    符合预期。

  • 相关阅读:
    Spring Boot (20) 拦截器
    Spring Boot (19) servlet、filter、listener
    Spring Boot (18) @Async异步
    Spring Boot (17) 发送邮件
    Spring Boot (16) logback和access日志
    Spring Boot (15) pom.xml设置
    Spring Boot (14) 数据源配置原理
    Spring Boot (13) druid监控
    Spring boot (12) tomcat jdbc连接池
    Spring Boot (11) mybatis 关联映射
  • 原文地址:https://www.cnblogs.com/qyf2199/p/14597621.html
Copyright © 2011-2022 走看看