当前位置 博文首页 > 阳阳的博客:【设计模式】单例模式

    阳阳的博客:【设计模式】单例模式

    作者:[db:作者] 时间:2021-08-14 21:03

    ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?单例模式

    在日常的开发过程中,我们需要使用到设计模式,而单例模式可谓是最常见的一种。正确的使用单例模式,不仅可以降低内存的使用率,也可以提升整个程序的运行效率。下面我来谈谈自己对单例模式的理解。


    【1】懒汉式

    特点:

    (1)是一种牺牲时间换取空间的策略

    (2)懒加载,只在需要的时候才实例化对象

    
    public class Singleton {
        private static Singleton instance;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
        
    }

    然而这样的单例模式存在缺点:

    (1)多线程情况下,容易被多个线程实例化出多个对象,违背”单例“的原则


    【2】线程安全的懒汉式

    考虑到在多线程的情况下,懒汉式存在问题,最普遍的想法,就是给getInstance()方法加上同步锁

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

    不过,由于对整个方法加锁,系统性能带来严重的降低,每次获取单例都需要先获取同步锁,而我们只需要在创建单例时加锁就行了。


    【3】线程安全且性能较好的懒汉式(DCL)

    public class SingletonDCL {
        private volatile static SingletonDCL instance;
    
        private SingletonDCL() {
        }
    
        public static SingletonDCL getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new SingletonDCL();
                    }
                }
            }
            return instance;
        }
    
    }

    这样的写法也称双重检验锁法(DCL),只在实例为null时才进行加锁。

    双重判断使得当两个线程同时运行到加锁前的一步时,再顺序获得锁之后不会创建出两个实例来。

    ?

    第二种方法和第三种方法的效率比较

    public class Test {
    
        public static void main(String args[]) throws InterruptedException {
            long start_1 = System.currentTimeMillis();
            List<Thread> list1 = new ArrayList<>();
    
            //使用20000个线程去获取单例
            for (int i = 0; i < 20000; i++) {
                Thread thread1 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Singleton.getInstance();
                    }
                });
                list1.add(thread1);
                thread1.start();
            }
    
            //等待线程执行完毕
            for (Thread thread : list1) {
                thread.join();
            }
            long end_1 = System.currentTimeMillis();
            System.out.println("非DCL方法的耗时:" + (end_1 - start_1));
    
    
            long start_2 = System.currentTimeMillis();
            List<Thread> list2 = new ArrayList<>();
            for (int i = 0; i < 20000; i++) {
                Thread thread2 = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        SingletonDCL.getInstance();
                    }
                });
                list2.add(thread2);
                thread2.start();
            }
            for (Thread thread : list2) {
                thread.join();
            }
            long end_2 = System.currentTimeMillis();
            System.out.println("DCL方法的耗时:" + (end_2 - start_2));
        }
    }

    输出为

    可以看得出使用双重检验锁之后,系统的性能带来了提高。


    【4】饿汉式

    特点:

    (1)是一种牺牲空间换取时间的策略

    (2)类加载时,就直接实例化对象

    (3)使用类加载机制,避免了多线程的同步问题

    public class Singleton {
        private Singleton() {
        }
    
        private static Singleton instance = new Singleton();
    
        public static Singleton getInstance() {
            return instance;
        }
    
    }

    这样的写法确实很简单,但存在以下的缺点

    (1)无论当前类的实例什么时候用,都在类加载时一起实例化所有使用此模式创建的实例,大量实例存在于内存中,内存的使用率提高


    【5】静态内部类

    public class Singleton {
        private Singleton() {
        }
    
        private static class SingletonHolder {
            private static Singleton instance = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    
    }

    这样做得好处是

    (1)静态内部类不会在类加载时就创建此类的实例,只有在显示调用此静态内部类时,此静态内部类才会被加载,内部的对象实例才会被赋值,起到懒加载的作用。

    (2)和饿汉式一样,使用类加载机制,避免多线程下的同步问题。


    【6】枚举(“单例的最佳实践”)

    public enum EnumSingleton {
    
        INSTANCE;
        Resource instance;
    
        EnumSingleton() {
            instance = new Resource();
        }
    
        public Resource getInstance() {
            return instance;
        }
    
    }

    Effective Java作者Josh Bloch 提倡的方式,Resource类代表需要应用单例模式的资源。在main方法中通过EnumSingleton.INSTANCE.getInstance()获得Resource的单例,
    那么这个单例是如何被保证的呢??
    在枚举类中,构造方法是私有的,在我们访问枚举实例时会执行构造方法,同时每个枚举实例被public static final修饰且为EnumSingleton类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。?
    也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的Resource也被保证实例化一次。?


    【7】登记式(不常用)

    将整个项目中用到的单例作集中化管理,即使用容器进行管理。

    public class SingletonManager {
        private static Map<String, Object> map = new HashMap<>();
    
        private SingletonManager() {
        }
    
        public static void addSingleton(String key, Object singleton) {
            if (!map.containsKey(key)) {
                map.put(key, singleton);
            }
        }
    
        public static Object getSingleton(String key) {
            return map.get(key);
        }
    
    }

    这样写的好处是什么呢?

    (1)提供统一的接口增加或获取实例,提高对单例的管理效率


    碍于博主才疏学浅,对单例模式的讨论也就这么多了,我会在以后的学习还有接下来的工作中,不断地修改博客,尽量将错误减少到最少,将优质的博文呈现给大家。

    cs