Java设计模式-单例模式的7种写法详解(上)

Java设计模式-单例模式的7种写法详解(上)

参照B站尚硅谷官方视频资源:https://www.bilibili.com/video/av57936239?p=33

0.前言

单例模式: 确保此类只有一个实例,并提供获取实例的方法。

作用: 可以保持在一个应用程序生命周期内,所引用对象的实例均为同一个

使用场景:例如工具类,使用了单例模式后,可避免重复创建实例造成的资源浪费。

单例模式分为以下7种:

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 双重检查
  6. 静态内部类
  7. 枚举

也可说是5种,只是懒汉饿汉各自又有2种不同的实现方式。

此外还介绍一种可能在面试中会提到的错误写法:懒汉式(同步代码块),实际是一种线程不安全的写法。

懒汉:类装载的时候不实例化,使用时才实例化, 即延迟加载,需要考虑线程安全

饿汉:类装载的时候就实例化(管你之后用不用,先实例化了),本身就线程安全

1.饿汉式(静态常量)实现单例模式

1.1 实现步骤

通过以下三个步骤实现

  1. 私有化构造函数(这样就避免了外部通过new创建类的实例)
  2. 类的内部创建对象
  3. 对外暴露一个获取实例的静态方法(通常写为getInstance)

1.2 具体编码

具体编码如下:

/**
 * 1.饿汉式(静态常量)实现单例模式
 * 步骤:
 * 1. 私有化构造函数
 * 2. 类的内部创建对象
 * 3. 对外暴露一个获取实例的静态方法
 */
public class EagerSingleton1 {

    // 1.私有化构造函数
    private EagerSingleton1(){
        //初始化工作...
    }

    // 2.本类内部创建对象的实例
    private final static EagerSingleton1 instance=new EagerSingleton1();

    //3.对外提供一个获取实例的静态方法
    public static EagerSingleton1 getInstance() {
        return instance;
    }
}

1.3 测试验证

测试代码如下:

//单例模式测试类
public class SingleTest {
    public static void main(String[] args) {
        //测试 1.饿汉式(静态常量)实现单例模式
        EagerSingleton1 eagerSingleton11=EagerSingleton1.getInstance();
        EagerSingleton1 eagerSingleton12=EagerSingleton1.getInstance();
        //判断这两实例 是否为同一个
        System.out.println(eagerSingleton11==eagerSingleton12);
    }
}

测试结果:

true

1.4 阶段小结

使用饿汉式(静态常量)实现单例模式:

优点:写法简单;使用getInstance()方法类装载时实例化,所以线程安全。

缺点:没有懒加载,所以若一直没使用该实例则导致了内存浪费

2.饿汉式(静态代码块)实现单例模式

2.1 实现步骤

和使用静态常量的饿汉式单例模式大致相同:

  1. 私有化构造函数(这样就避免了外部通过new创建类的实例)
  2. 在静态代码块种创建实例
  3. 对外暴露一个获取实例的静态方法(通常写为getInstance)

2.2 具体编码

/**
 * 饿汉式(静态代码块)实现单例模式
 */
public class EagerSingleton2 {
    // 1.私有化构造函数
    private EagerSingleton2(){
        //初始化工作...
    }
    // 2.静态代码块中创建实例
    private  static EagerSingleton2 instance;
    static {
        instance=new EagerSingleton2();
    }

    //3.对外提供一个获取实例的静态方法
    public static EagerSingleton2 getInstance() {
        return instance;
    }
}

2.3 测试验证

//单例模式测试类
public class SingleTest {
    public static void main(String[] args) {
        //测试 2.饿汉式(静态代码块)实现单例模式
        EagerSingleton2 eagerSingleton21=EagerSingleton2.getInstance();
        EagerSingleton2 eagerSingleton22=EagerSingleton2.getInstance();
        //判断这两实例 是否为同一个
        System.out.println(eagerSingleton21==eagerSingleton22);
    }
}

测试结果:

true

2.4 阶段小结

与第一种使用静态常量的饿汉单例模式类似,只是实例化对象的过程放在了静态代码块中,也是类装载时实例化对象。

3.懒汉式(线程不安全)实现单例模式

3.1实现步骤

  1. 私有化构造函数
  2. 在获取实例的方法(getInstance)中判断实例是否为空,为空则实例化,否则直接返回实例。

3.2具体编码

/**
 * 3.懒汉式(线程不安全)单例模式 (1)
 */
public class LazySingleton1 {
    private static LazySingleton1 lazySingleton1;
    //1.私有化构造函数
    private LazySingleton1() {
    }
    //对外暴露获取实例的方法
    //外面需要使用该实例时(即调用getInstance()),才创建实例
    public static LazySingleton1 getInstance() {
        if (lazySingleton1 == null) {
            //没有才创建
            lazySingleton1 = new LazySingleton1();
        }
        return lazySingleton1;
    }
}

3.3测试验证

//单例模式测试类
public class SingleTest {
    public static void main(String[] args) {
        //测试 3.懒汉式(线程不安全)单例模式 (1)
        LazySingleton1 lazySingleton11 = LazySingleton1.getInstance();
        LazySingleton1 lazySingleton12 = LazySingleton1.getInstance();
        //判断这两实例 是否为同一个
        System.out.println(lazySingleton11 == lazySingleton12);
    }
}

测试结果:

true

3.4阶段小结

优点:实现了实例懒加载

缺点:单线程可以使用,但在多线程中,可能存在这种情况:一个线程执行完 if (lazySingleton1 == null)后还没来得及执行条件成立的代码,另一个线程也执行了 if (lazySingleton1 == null),这样多个线程进入会导致创建多个实例,没有达到单例的效果。

即:多线程不可使用

4.懒汉式(线程安全,同步方法)实现单例模式

和第三种唯一的区别就是在获取实例的方法(getInstance)加入了synchronized关键字,使其为同步方法。

4.1实现步骤

  1. 私有化构造函数
  2. 在获取实例的方法(getInstance)中判断实例是否为空,为空则实例化,否则直接返回实例。且此方法(getInstance)为同步方法,即使用了synchronized关键字。

4.2具体编码

/**
 * 4.懒汉式(线程安全,同步方法)实现单例模式(1)
 */
public class LazySingleton2 {
    private static LazySingleton2 lazySingleton2;

    //1.私有化构造函数
    private LazySingleton2() {
    }

    //对外暴露获取实例的方法
    //外面需要使用该实例时(即调用getInstance()),才创建实例
    public static synchronized LazySingleton2 getInstance() {
        if (lazySingleton2 == null) {
            //没有才创建
            lazySingleton2 = new LazySingleton2();
        }
        return lazySingleton2;
    }
}

4.3测试验证

//单例模式测试类
public class SingleTest {
    public static void main(String[] args) {
        //测试 4.懒汉式(线程安全,同步方法)单例模式 (2)
        LazySingleton2 lazySingleton21 = LazySingleton2.getInstance();
        LazySingleton2 lazySingleton22 = LazySingleton2.getInstance();
        //判断这两实例 是否为同一个
        System.out.println(lazySingleton21 == lazySingleton22);
    }
}

4.4阶段小结

优点:实现了对象实例化的懒加载;线程安全。

缺点:效率低,因为实例的初始化只需一次,但是每次调用getInstance都需要同步。(就是同步方法真正有正面作用的执行次数只有一次,而之后的执行由于需要同步都拖累了系统运行)

实际开发不推荐使用

5.一种错误的写法:懒汉式(同步代码块)

这是错误的写法!错误的写法!错误的写法!

面试可能会有这样的问题,所以还是记录了一下。

这种写法的由来是想改进第四种:懒汉式(线程安全,同步方法) 效率低的问题,但是在多线程下,依然会有多个线程都进入if判断成立的代码块,所以依然会有生成多个实例的风险。所以带来了线程不安全的问题。

5.1实现步骤

  1. 私有化构造函数
  2. 在getInstance方法中if判断实例为空后,将实例化对象的这一段代码加上synchronized关键字使其成为同步代码块。

5.2具体编码

/**
 * 5.懒汉式(线程不安全,同步代码块)单例模式 (3)  
 * 
 * 错误写法!了解缺陷即可
 * 错误写法!了解缺陷即可
 * 错误写法!了解缺陷即可
 */
public class LazySingleton3 {

    private static LazySingleton3 lazySingleton3;

    //1.私有化构造函数
    private LazySingleton3() {
    }

    //对外暴露获取实例的方法
    //外面需要使用该实例时(即调用getInstance()),才创建实例
    public static LazySingleton3 getInstance() {
        if (lazySingleton3 == null) {
            //没有才创建
            synchronized (LazySingleton3.class){
                lazySingleton3 = new LazySingleton3();
            }
        }
        return lazySingleton3;
    }
}

5.3测试验证

由于本身是错误的写法,就不测了。

5.4阶段小结

这种写法线程不安全,不能起到线程同步的作用!!

由于可能多个线程进入if成立的代码段,所以那个同步代码块根本不起作用,不能使线程同步。

这种写法实际开发不能用!,但面试可能会问,还是了解一下。

本文为看视频后的笔记摘要:
B站尚硅谷官方视频资源《尚硅谷图解Java设计模式韩顺平老师2019力作》:https://www.bilibili.com/video/av57936239?p=33

会在下一篇笔记中提及剩下三种单例模式的写法:
双重检查单例模式,静态内部类单例模式,枚举单例模式
Java设计模式-单例模式的7种写法详解(下)

发布了67 篇原创文章 · 获赞 32 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_42391904/article/details/104111652