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

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

一、单例模式

  所谓的单例设计模式,就是采取一定的方法保证整个的软件系统中,对某个类只能存在一个对象实例,并且该类只能提供一个取得对象实例的方法(静态方法)。比如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工厂等)

原文地址:https://www.cnblogs.com/rmxd/p/12635430.html