• 设计模式六大原则之单例模式


      前言:不断学习就是程序员的宿命

    一、单例模式

      所谓的单例设计模式,就是采取一定的方法保证整个的软件系统中,对某个类只能存在一个对象实例,并且该类只能提供一个取得对象实例的方法(静态方法)。比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建session对象。SessionFactory并不是轻量级的

      单例模式有以下8种方式:

      

    二、饿汉式(静态常量)

    public class Singleton01 {
        //本类内部创建对象实例
        private final static Singleton01 instance=new Singleton01();
        //构造器私有化,外部不能new
       private Singleton01(){}
       //提供一个公有静态方法,返回实例对象
        private static Singleton01 getInstance(){
           return instance;
        }
    
        public static void main(String[] args) {
            //测试
            Singleton01 instance1 = Singleton01.getInstance();
            Singleton01 instance2 = Singleton01.getInstance();
            System.out.println(instance1==instance2);
            System.out.println(instance1.hashCode());
            System.out.println(instance1.hashCode());
        }
    }
    饿汉式(静态常量)

    分析:

      (1)优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

      (2)缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

    三、饿汉式(静态代码块)

    public class Singleton02 {
        //本类内部创建对象实例
        private  static Singleton02 instance;
        //构造器私有化, 外部不能new
        private Singleton02(){}
        // 在静态代码块中,创建单例对象
        static {
            instance = new Singleton02();
        }
        //提供一个公有的静态方法,返回实例对象
        public static Singleton02 getInstance() {
            return instance;
        }
        public static void main(String[] args) {
            //测试
            Singleton02 instance = Singleton02.getInstance();
            Singleton02 instance2 = Singleton02.getInstance();
            System.out.println(instance == instance2); // true
            System.out.println("instance.hashCode=" + instance.hashCode());
            System.out.println("instance2.hashCode=" + instance2.hashCode());
        }
    }
    饿汉式(静态代码块)

    分析:

      (1)这种方式和上述方式类似,只不过将类实例化的过程放在了静态代码块中,也就是在装载的时候执行静态代码中的代码,初始化类的实例

      (2)结论:单例方式可用,但可能会造成内存浪费

    四、懒汉式(线程不安全)

    /**
     * @ClassName: Singleton03
     * @Description: 懒汉式-线程不安全
     * @Author: xiedong
     * @Date: 2020/4/5 0:19
     */
    public class Singleton03 {
        private static Singleton03 instance;
    
        private Singleton03() {}
    
        //提供一个静态的公有方法,当使用到该方法时,才去创建 instance
        public static Singleton03 getInstance() {
            if(instance == null) {
                instance = new Singleton03();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            System.out.println("懒汉式1 , 线程不安全~");
            Singleton03 instance = Singleton03.getInstance();
            Singleton03 instance2 = Singleton03.getInstance();
            System.out.println(instance == instance2); // true
            System.out.println("instance.hashCode=" + instance.hashCode());
            System.out.println("instance2.hashCode=" + instance2.hashCode());
        }
    }
    懒汉式(线程不安全)

    分析:

      (1)起到了Lazy Loading的效果,但是只能在单线程下使用

      (2)如果在多线程下,一个线程进入if(instance==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例,所以在多线程环境下不可使用这种方式。

      (3)结论:实际开发中,不要使用这种方式

    五、懒汉式(同步方法保证线程安全)

    /**
     * @ClassName: Singleton04
     * @Description: 懒汉式-同步方法
     * @Author: xiedong
     * @Date: 2020/4/5 0:53
     */
    public class Singleton04 {
        private static Singleton04 instance;
    
        private Singleton04() {}
    
        //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
        //即懒汉式
        public static synchronized Singleton04 getInstance() {
            if(instance == null) {
                instance = new Singleton04();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            System.out.println("懒汉式2 , 线程安全~");
            Singleton04 instance = Singleton04.getInstance();
            Singleton04 instance2 = Singleton04.getInstance();
            System.out.println(instance == instance2); // true
            System.out.println("instance.hashCode=" + instance.hashCode());
            System.out.println("instance2.hashCode=" + instance2.hashCode());
        }
    }
    懒汉式(同步方法)

    分析:

      (1)解决线程安全问题

      (2)效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想要获得该类实例,直接return就行了,方法同步效率太低了。

      (3)结论:在实际开发中,不推荐使用这种方式

    六、懒汉式(同步代码块保证线程安全)

    /**
     * @ClassName: Singleton05
     * @Description: 懒汉式-同步代码块
     * @Author: xiedong
     * @Date: 2020/4/5 0:56
     */
    public class Singleton05 {
        private static Singleton05 instance;
    
        private Singleton05() {
        }
        
        public static Singleton05 getInstance() {
            if (instance == null) {
                synchronized (Singleton05.class) {
                    instance = new Singleton05();
                }
            }
            return instance;
        }
    
        public static void main(String[] args) {
            Singleton05 instance = Singleton05.getInstance();
            Singleton05 instance2 = Singleton05.getInstance();
            System.out.println(instance == instance2); // true
            System.out.println("instance.hashCode=" + instance.hashCode());
            System.out.println("instance2.hashCode=" + instance2.hashCode());
        }
    }
    同步代码块

    分析:

      (1)这种方式,本意是想对上述(同步方法)实现方式的改进,因为前面同步方法效率太低了,改为同步产生实例化的代码块

      (2)但是这种同步并不能起到线程同步的作用。假如一个线程进入了if(instance==null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

      (3)结论:实际开发中,不能使用这种方式

    七、双重检查

    /**
     * @ClassName: Singleton06
     * @Description: 双重检查
     * @Author: xiedong
     * @Date: 2020/4/5 1:07
     */
    public class Singleton06 {
        private static volatile Singleton06 instance;
    
        private Singleton06() {}
    
        //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
        //同时保证了效率, 推荐使用
    
        public static synchronized Singleton06 getInstance() {
            if(instance == null) {
                synchronized (Singleton06.class) {
                    if(instance == null) {
                        instance = new Singleton06();
                    }
                }
    
            }
            return instance;
        }
        public static void main(String[] args) {
            System.out.println("双重检查");
            Singleton06 instance = Singleton06.getInstance();
            Singleton06 instance2 = Singleton06.getInstance();
            System.out.println(instance == instance2); // true
            System.out.println("instance.hashCode=" + instance.hashCode());
            System.out.println("instance2.hashCode=" + instance2.hashCode());
    
        }
    }
    双重检查

    分析:

      (1)Double-Check概念是多线程开发中常使用到的,如代码中所示,两次if(singleton==null)检查,这样就保证线程安全了。

      (2)这样实例化代码只用执行一次,后面再次访问到时,判断if(singleton==null)直接return实例对象,也避免了反复进行方法同步。

      (3)线程安全;延迟加载;效率较高

      (4)结论:在实际开发中,推荐使用这种单例设计模式

    八、静态内部类

    /**
     * @ClassName: Singleton7
     * @Description:
     * @Author: xiedong
     * @Date: 2020/4/5 1:20
     */
    public class Singleton7 {
        private static volatile Singleton7 instance;
    
        //构造器私有化
        private Singleton7() {}
    
        //写一个静态内部类,该类中有一个静态属性 Singleton
        private static class SingletonInstance {
            private static final Singleton7 INSTANCE = new Singleton7();
        }
    
        //提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
    
        public static synchronized Singleton7 getInstance() {
    
            return SingletonInstance.INSTANCE;
        }
        public static void main(String[] args) {
            System.out.println("使用静态内部类完成单例模式");
            Singleton7 instance = Singleton7.getInstance();
            Singleton7 instance2 = Singleton7.getInstance();
            System.out.println(instance == instance2); // true
            System.out.println("instance.hashCode=" + instance.hashCode());
            System.out.println("instance2.hashCode=" + instance2.hashCode());
    
        }
    }
    静态内部类实现单例

    分析:

      (1)这种方式采用了类装载的机制来保证初始化实例时只有一个线程

      (2)静态内部类方式在Singleton07类(外部类)被装载时并不会立即实例化,而是在需要实例化时,调用getInstance()方法,才会装载SingletonInstance类,从而完成Singleton的实例化

      (3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

      (4)优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

      (5)结论:推荐使用

    九、枚举

    /**
     * @ClassName: Singleton08
     * @Description:
     * @Author: xiedong
     * @Date: 2020/4/5 1:30
     */
    public class Singleton08 {
        public static void main(String[] args) {
            Singleton instance = Singleton.INSTANCE;
            Singleton instance2 = Singleton.INSTANCE;
            System.out.println(instance == instance2);
    
            System.out.println(instance.hashCode());
            System.out.println(instance2.hashCode());
    
            instance.sayOK();
        }
    }
    
    //使用枚举,可以实现单例, 推荐
    enum Singleton {
        INSTANCE; //属性
    
        public void sayOK() {
            System.out.println("ok~");
        }
    }
    枚举实现单例

    分析:

      (1)这里借助JDK1.5中添加的枚举来实现单例模式。不仅能够避免多线程同步问题,而且还能防止反序列化重新创建新的对象

      (2)这种方式是Effective Java作者Josh Bloch提倡的方式

      (3)结论:推荐使用

    十、单例模式在JDK应用举例

      可以发现,JDK中使用饿汉式-静态属性

    十一、总结

    (1)单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

    (2)当想实例化一个单例类的时候,必须要记住使用相应获取对象的方法,而不是new

    (3)单例模式的使用场景:需要频繁的创建和销毁对象、创建对象时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

  • 相关阅读:
    迭代器和生成器
    New test
    MySQL笔记整理
    Python基础数据类型
    Python基础
    Python入门知识
    Linux / MacOS 下Redis 安装、配置和连接
    NuGet的使用心得
    简单工厂模式和策略模式的区别与结合
    NuGet的使用和服务搭建
  • 原文地址:https://www.cnblogs.com/rmxd/p/12635430.html
Copyright © 2020-2023  润新知