设计模式之单例模式详解

单例模式(Singleton)



  • 这篇博客算是我真正开始写博客的开篇吧!
  • 之前太菜啦~,写的博文都已经删掉啦,自己都看不下去了,哈哈
  • 如果有什么不对的,还希望各位大神指出,我会非常感激的!!!
  • 希望能够帮助到正在学习单例模式的你~~
  • 本文代码_Github地址

什么是单例模式

  • 就是在整个程序周期中,某个类(单例类)只有一个实例存在,而且这个类提供了一个得到这个实例的静态方法
  • 注意:Application类就是一个单例模式,在整个程序中只有一个实例;就是说,你在一个活动中拿到Application这个实例后,对它进行更改,那么,其他活动拿到的实例也会被改变

单例模式的特点

  • 单例类的 构造方法是private的,意味着在这个类之外,不能通过构造方法来创建实例
  • 单例类必须 自己创建自己的唯一实例
  • 并给其他对象提供这个实例(通过静态方法的形式)
  • 单例一般命名为 instance

两个需要了解的基本概念

简单理解——类加载机制

  • 类加载机制这个东西涉及到了JVM方面的知识,所以,对于本文的单例模式来说,有个了解进行啦~
  • 类的加载过程分为五个阶段:加载 → 连接(验证 →准备 → 解析) → 初始化
    • 我们这里就了解一下:类什么时候进行初始化?
    • 在Java代码中,当类被使用的时候,就进行了初始化
      • 创建一个类的实例
      • 访问类中的属性、方法,也包括反射
      • 初始化一个类的子类 (首先会先初始化它的父类)
      • JVM启动时标明的启动类(就是文件名和类名相同的那个类)

简单理解——延迟加载机制

  • 所谓延迟加载,简单的讲,就是当我们真正需要一个数据的时候,才会去创建这个数据
    • 联想到单例模式中,就是当我们需要一个类的单例的时候,才会去创建这个单例

单例模式的各种写法

  • 总结下来,一共有 6 种:饿汉式、懒汉式、双重效验锁、静态内部类、缓存、枚举
    • 缓存那个方式这里就不做讲解啦,就是通过 map 集合来实现的,有兴趣的小伙伴自己去了解啦~
  • 特点
    • 饿汉模式没有实现延迟加载,其他都实现了
    • 饿汉模式、静态内部类都是类加载机制,天生无多线程问题

饿汉式

  • 最简单的单例模式!!
    • 顾名思义,就是太"饿"了,所以单例类被加载的时候就创建好了单例
  • 线程安全天生线程安全
    • 为什么呢?因为这个特殊的类加载机制呀!!!
    • 一个类在整个声明周期中,只会被加载一次,而这唯一的一次就已经将单例创建好了
    • 无论有几个线程,每次拿到的单例也将会是这仅有的一个!
  • 适用于:单例 占用内存比较小,在 初始化时 就会 被用到 的情况
  • 特点: 1. 单例类加载的时候就会创建单例对象 (类加载机制); 2. 天生线程安全
  • 缺点:由于其特殊的类加载机制,如果没有用到这个单例类的话,就会 浪费掉内存
  • 优点:类加载机制,保证天生线程安全,避免了多线程同步问题
/**
 * @author: Richard
 * @date: 2019/7/18
 * @describe: 单例类_饿汉式,在类加载时就创建了实例;
 */
public class MySingletonTest_1 {

    //在类加载时就创建了实例;
    private static MySingletonTest_1 instance = new MySingletonTest_1();

    //构造方法是private的
    private MySingletonTest_1() {
    }

    //提供一个静态方法,返回单例
    public static MySingletonTest_1 getInstance() {
        return instance;
    }
}

懒汉式

  • 顾名思义,比较“懒”,所以在要使用的时候才会去加载单例实例,这也就是延迟加载
  • 线程安全不安全
    • 因为这个延迟加载,单例的初始化是在提供单例的方法中进行的,当有多个线程去调用提供单例的方法的时候,会多次创建实例
    • 解决方法:给提供单例的方法上加上锁,使线程一个一个的进行到方法内部
  • 适用于:单例 使用的次数少,并且创建的单例 占用的资源较多 的情况
  • 特点:1. 就是延迟加载,在需要的时候才会去加载单例;2. 实现单例通过 判空 操作来实现的
  • 缺点:多个线程可能会 并发的调用 类中提供单例的方法,导致存在多个实例,出现 多线程同步问题
  • 优点:单例是在要 需要的时侯才会去创建,节约资源
/**
 * @author: Richard
 * @date: 2019/7/19
 * @describe: 单例模式_懒汉式,这是改良的线程安全的模式,一般的就去掉锁
 */
public class MySingletonTest_2 {
    
    //开始并没有实例化
    private static MySingletonTest_2 instance_2 = null;

    //构造方法私有
    private MySingletonTest_2() {
    }

    //提供一个静态方法,获取单例实例,在方法上加上锁,保证线程安全
    //synchronized修饰的同步方法比一般方法要慢很多,如果多次调用getInstance(),累积的性能损耗就比较大了
    public static synchronized MySingletonTest_2 getInstance() {

        //判断是否为空,从而控制实例,数量
        if (instance_2 == null) {
            instance_2 = new MySingletonTest_2();
        }
        return instance_2;
    }

}

双重效验锁

  • 线程安全安全

    • 因为加了 线程同步锁 和 判空操作,就避免了 线程并发问题
    • 这个锁的位置与 懒汉式的改良版 不同,比较特殊,下面进行讲解
  • 适用于推荐

  • 特点:1. 实现了延迟加载;2. 进行了两次判控操作,并且锁住了类,解决了线程并发问题;3. 还有改良版的 volatile 关键字的使用

  • 缺点:(了解就行,涉及到的东西太过深入了)

    • 双检锁隐患
      • 这个问题的关键就在于由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。
      • 在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。
      • 此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错
  • 优点:解决了线程并发问题,同时还解决了执行效率问题

不过还好在JDK1.5及之后版本增加了volatile关键字,解决了双重校验锁会失效的问题
volatile变量可以保证下一个读取操作会在前一个写操作之后发生

/**
 * @author: Richard
 * @date: 2019/7/19
 * @describe: 单例模式_双重效验锁
 */
public class MySingletonTest_3 {

    //开始并没有实例化
    //volatile变量可以保证下一个读取操作会在前一个写操作之后发生
    private static volatile MySingletonTest_3 instance_3 = null;

    //构造方法为私有的
    private MySingletonTest_3() {
    }

    //提供一个静态方法,获取实例
    public static MySingletonTest_3 getInstance() {

        //第一次判断:是否存在实例
        if (instance_3 == null) {
            //加一个锁,防止线程并发调用
            synchronized (MySingletonTest_3.class) {
                //第二次判断:当有两个线程都执行到了第一次判断,都认为instance_3是null时,
                // 当一个线程执行完了同步代码块创建实例后,后面的线程还是会执行一次,又创建了一个实例;
                if (instance_3 == null) {
                    instance_3 = new MySingletonTest_3();
                }
            }
        }
        return instance_3;
    }
}

静态内部类

  • 线程安全安全
    • 和饿汉式的线程安全类似,都是类加载机制,一个类在整个生命周期中只会被加载一次,当调用方法获取这个单例的时候,内部类将会被加载,且只有这一次
  • 适用于推荐
  • 特点:1. 使用 类加载机制 来保证只创建一个实例,不存在多线程并发的问题
  • 缺点:第一次加载时反应不够快
  • 优点:因为 单例实例在内部类中,因此只要 不使用 内部类可以同时保证 延迟加载线程安全
/**
 * @author: Richard
 * @date: 2019/7/19
 * @describe: 单例模式_静态内部类
 */
public class MySingletonTest_4 {

    //私有构造方法
    private MySingletonTest_4() {
    }

    //静态内部类
    private static class MySinleton {
        private static MySingletonTest_4 mySingletonTest_4 = new MySingletonTest_4();
    }

    //静态方法,访问内部类属性,获取单例实例
    public static MySingletonTest_4 getInstance() {
        return MySinleton.mySingletonTest_4;
    }
}

枚举

  • 虽然枚举可以实现单例模式,但是在实际的开发中,并不推荐使用 枚举来实现单例模式
  • 线程安全安全
    • 这个我在想,应该和类加载机制类似的吧,枚举值只会被实例化一次,所以,在整个生命周期中,只会有这一次创建的实例
  • 适用于:单例 占用内存比较小,在 初始化时 就会 被用到 的情况
  • 特点:通过枚举实例只能被实例化一次的特性,实现了单例实例的唯一性
  • 缺点:我认为,代码太复杂啦~
  • 优点: 在我们访问 枚举实例 时会执行构造方法,同时每个枚举实例都是默认static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。也就是说,因为enum中的实例被保证 只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
/**
 * @author: Richard
 * @date: 2019/7/19
 * @describe: 单例模式_枚举
 */
//单例类
public class MySingletonTest_5 {

    //私有构造方法
    private MySingletonTest_5() {};

    //提供一个静态方法,获取单例实例
    public static MySingletonTest_5 getInstance() {

        return Singlrton.INSTANCE.getMySingletonTest_5();
    }

    private static enum Singlrton {

        //枚举值:是static、final类型的、只能被实例化一次
        INSTANCE;

        MySingletonTest_5 mySingletonTest_5 = null;

        //枚举的私有构造方法里面进行实例化
        //JVM会保证此方法绝对只调用一次
        private Singlrton() {
            mySingletonTest_5 = new MySingletonTest_5();
        }

        //提供一个静态方法,返回实例
        public MySingletonTest_5 getMySingletonTest_5() {
            return mySingletonTest_5;
        }
    }

}

应用

  • OKHttp的 OkHttpClient对象可以使用双重验证锁来实现单例模式
  • EventBus的 getDefault() 方法得到的实例就是一个单例,我以前还傻傻的自己写一个EventBus的单例呢,后面点进去看了看源码才发现的,哈哈,真是太蠢了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值