您的位置:首页 > 其它

手撕设计模式之单例模式(详细解析)

2020-04-07 18:27 1666 查看

前言

单例模式主要用来保证系统中某个类的实例对象的唯一性,是最简单的一种设计模式,而且在面试中也经常会被问到,是非常值得我们去学习的。如果你们面试遇到了哪些设计模式的考察,也欢迎留言,我会及时发新的博文。

文章目录

  • 4. 多线程环境中的单例模式
  • 参考资料
  • 1. 模式定义

    单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并像整个系统提供这个实例,这个类称为单例类,它会提供全局访问的方法。单例模式是对象创建型模式的一种。

    单例模式的三个要点:

    • 某个类只能有一个实例
    • 必须自行创建这个实例
    • 必须自行向整个系统提供这个实例

    2. 模式实现

    要想实现单例模式,我们可以从它的三个要点入手:

    首先是单例的唯一性,既然单例是唯一的,我们毫无疑问应该给他加上

    static
    关键字,又因为这个对象不应该直接暴露,所以还要加上
    private
    进行访问限定。

    private static Singleton instince = null;

    接着,就是这个对象需要由单例类自行创建,这时我们就应该屏蔽外界访问该类初始化方法的接口,也就是用

    private
    修饰构造函数。

    private Singleton() {
    }

    最后一点就比较容易实现了,给公有工厂方法

    static
    关键字,即可实现向整个系统提供这个实例。

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

    将以上代码整合一下,就可以得到如下的单例模式的代码实现模板:

    public class Singleton {
    //静态私有成员变量,保证实例的唯一性
    private static Singleton instince = null;//私有构造函数,保证实例由类自行创建
    private Singleton() {
    }// 静态公有工厂方法,向系统提供这个唯一实例
    public static Singleton getInstance() {
    if (instance == null) {
    instince = new Singleton();
    }
    return instance;
    }}
    

    下面是针对上面单例模式实现模板的客户端测试代码:

    public Client {
    public static void main(String []args) {
    Singleton s1 = Singleton.getInstance();
    Singleton s2 = Singleton.getInstance();
    System.out.println(s1 == s2); //true
    }
    }

    3. 单例模式的拓展

    单例模式可以分为懒汉式单例饿汉式单例,它们的区别主要在单例类对象的初始化时间上。下面我们来详细讲解:

    3.1 懒汉式单例

    懒汉式单例模式的结构图如下图所示:

    在模式图中,我们可以看到这实例对象在创建类的时候并没有初始化,而是等到有人调用

    getInstance()
    方法获取实例的时候才进行实例对象的初始化,这也恰恰体现了懒汉式的这种“懒惰行为”。

    下面是以“身份证号码类”作为例子,编写的懒汉式单例代码。

    public class IdentityCardNo {
    private String no;
    
    //某个类只能有一个实例
    private static IdentityCardNo instince = null;
    
    //必须自行创建这个实例
    private IdentityCardNo() {
    
    }
    
    //必须自行向整个系统提供这个实例
    public static IdentityCardNo getInstance() {
    if (instince == null) {
    System.out.println("第一次办理身份证,分配新号码");
    instince = new IdentityCardNo();
    instince.setIdentityCardNo("400000199710301111");
    } else {
    System.out.println("重复办理身份证,获取旧号码");
    }
    return instince;
    }
    public String getIdentityCardNo() {
    return no;
    }
    
    public void setIdentityCardNo(String no) {
    this.no = no;
    }
    }

    可以仿造上面的方法,写一个Client类来进行检验。

    3.2 饿汉式单例

    饿汉式单例模式的结构图如下图所示:

    由于饿汉式单例的“饥饿”特性,使得它在类加载阶段就对单例类对象进行了初始化,从上面的结构图也可以看出这一点。

    下面代码是上面那个例子的饿汉式实现:

    public class IdentityCardNo {
    private String no;
    
    //某个类只能有一个实例
    private static IdentityCardNo instince = new IdentityCardNo();
    
    //必须自行创建这个实例
    private IdentityCardNo() {
    System.out.println("第一次办理身份证,分配新号码");
    }
    
    //必须自行向整个系统提供这个实例
    public static IdentityCardNo getInstance() {
    instince.setIdentityCardNo("400000199710301111");
    return instince;
    }
    public String getIdentityCardNo() {
    return no;
    }
    
    public void setIdentityCardNo(String no) {
    this.no = no;
    }
    }

    4. 多线程环境中的单例模式

    上面提到的懒汉式单例是线程不安全的,在多线程的环境下,对象的唯一性得不到保障。于是就有了下面几种线程安全的单例模式实现方法,其中第1,2种方法是对懒汉式的改进,第 3 中是对饿汉式的改进。

    4.1 延时加载

    我们可以用

    synchronized
    关键字修饰
    getInstance()
    方法,利用延时加载的方式保证在多线程环境下对象的唯一性。但是这种方法在容易造成线程拥塞,效率不高。

    实现代码如下:

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

    4.2 双重校验锁

    这种方式采用双锁机制,线程安全且在多线程情况下能保持高性能,是比较推荐使用的方法。

    实现代码如下:

    public class Singleton {
    private static Singleton instince = null;private Singleton() {
    }public static Singleton getInstance() {
    if (instance == null) {
    //只将synchronized关键字用在了初始化模块
    synchronized (Singleton.class) {
    if (install == null) {
    instince = new Singleton();
    }
    }
    }
    return instance;
    }
    }
    

    4.3 静态内部类

    这种方式通过给对象加上

    final
    关键字修饰,能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

    实现代码如下:

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

    参考资料

    1. 单例模式| 菜鸟教程
    2. 《Java设计模式》
    3. Java实现单例模式 | CSDN
    • 点赞
    • 收藏
    • 分享
    • 文章举报
    VeggieOrz 发布了89 篇原创文章 · 获赞 652 · 访问量 70万+ 私信 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: