美文网首页
单例模式

单例模式

作者: C调路过 | 来源:发表于2020-03-16 23:50 被阅读0次

    前言

    单例模式常用在一些全局唯一的管理类,避免对象重复创建,节省内存创建释放开销。

    饿汉模式

    每一次通过 new 创建的都是一个新的对象,为了保证全局唯一,我们就可以利用static关键字的特性,创建全局且线程共享的对象。这就是单例的第一种实现:饿汉模式

        public class Singleton {
    
            private static final Singleton instance = new Singleton();
    
            // 重要:写单例的第一件事就是将默认构造函数私有化,避免外部通过new来创建对象
            private Singleton() {
            }
    
            public static Singleton getInstance() {
                return instance;
            }
        }
    

    懒汉模式

    优秀的程序员懂得优化代码的时间空间,避免资源的浪费。在首次调用单例前,并没有初始化的必要,引出单例的第二种实现:懒汉模式

    1. 先私有化默认构造函数
    2. 在getInstance()方法中判断对象是否存在,没有就先new一个再返回,完美!
        public class Singleton2 {
    
            private static Singleton2 instance;
    
            // 重要:写单例的第一件事就是将默认构造函数私有化,避免外部通过new来创建对象
            private Singleton2() {
    
            }
    
            public static Singleton2 getInstance() {
                if (instance == null) {
                    instance = new Singleton2();
                }
                return instance;
            }
        }
    

    为了模拟正常初始化的运行时间,在制造函数中给1ms的sleep。

            private Singleton2() {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    

    然后我们在Main函数中疯狂使用。

            for (int i = 0; i < 100; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Singleton2.getInstance().toString());
                    }
                }).start();
    
            }
    
            -----------打印结果---------------
            singleton.Main$Singleton2@6d4bc03f
            singleton.Main$Singleton2@7f5b7c1b
            singleton.Main$Singleton2@b148489
            singleton.Main$Singleton2@30f51e1e
            singleton.Main$Singleton2@42f5503f
            singleton.Main$Singleton2@1bcc4803
            singleton.Main$Singleton2@7bae2ed5
            singleton.Main$Singleton2@701e866
            singleton.Main$Singleton2@4cf319d1
            singleton.Main$Singleton2@60174d9
            singleton.Main$Singleton2@71d9ab02
            singleton.Main$Singleton2@6d4bc03f
            singleton.Main$Singleton2@b20568f
            singleton.Main$Singleton2@71d9ab02
            singleton.Main$Singleton2@71d9ab02
            singleton.Main$Singleton2@71d9ab02
            singleton.Main$Singleton2@6299d4f3
            singleton.Main$Singleton2@9a662fb
            singleton.Main$Singleton2@1b73c3bb
            singleton.Main$Singleton2@3b637176
            singleton.Main$Singleton2@3b637176
            singleton.Main$Singleton2@3b637176
            singleton.Main$Singleton2@3b637176
    

    会发现在多线程并发环境下,不能保证单例的线程安全。在第一次请求创建过程中,另外的线程进行读取,对象instance仍为空,开始了另一个对象的创建。
    于是想到了给getInstance方法加锁,保证线程安全

            public static synchronized Singleton2 getInstance() {
                if (instance == null) {
                    instance = new Singleton2();
                }
                return instance;
            }
    

    但这样造成每次获取实例都要进行加锁耗时,我们想要的只是在第一次创建对象的时候加个锁。解决方式也很简单,于是给getInstance改写成给代码块加锁的方式。

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

    也就是所说的双重检查锁(double checked locking)。

    然而这并不是我们所见的标准DCL单例的写法,new Singleton2()创建对象并非是一个原子操作(类比 i = i + 1,首先是读取,加一,再赋值,需要多步完成),创建对象需要

    1. 分配内存
    2. 将对象指向内存
    3. 初始化对象

    在不同的编译器可能对2,3步骤进行重排序,即先赋值给对象然后才进行初始化,会导致getInstance返回的实例为null的情况。于是instance对象加上volatile修饰,保证对象初始化和赋值操作不进行重排,避免创建了对象但是instance为空的情况。

        private volatile static Singleton2 instance;
    

    静态内部类实现

    需要熟悉类加载机制。静态内部类的方式即能保证在使用时创建,也能保证线程安全,但是不能在初始化时传递参数。如我们需要对单例设置如Context时,选择DCL的方式更为合适。

    public static class Singleton3 {
    
        private Singleton3() {
            System.out.print("Singleton3初始化");
        }
    
        public static Singleton3 getInstance() {
            return SingletonHolder.INSTANCE;
        }
    
        private static class SingletonHolder {
            private static final Singleton3 INSTANCE = new Singleton3();
        }
    }
    

    其他

    还有通过枚举类来创建单例的,感觉平时不会用到。
    涉及知识点 static final synchronized关键字,类加载机制,原子性,重排序。

    相关文章

      网友评论

          本文标题:单例模式

          本文链接:https://www.haomeiwen.com/subject/rzyaehtx.html