前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单例模式详解

单例模式详解

作者头像
孟君
发布2019-08-29 18:17:54
5270
发布2019-08-29 18:17:54
举报

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。

本文将对单例模式进行较详细的介绍,主要包含如下几个部分的内容:

  1. 单例模式的意图和要点
  2. 两种不同的创建方式 :饿汉式 vs 懒汉式
  3. 懒汉式存在的问题
  4. 保证单例安全性的多种方式

一、单例模式的意图和要点

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

从上述描述中包含了3个要点:

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

二、饿汉式和懒汉式

根据单例的创建方式而言,单例的实现可以分为饿汉式和懒汉式两种。其中:

饿汉式是单例类被加载的时候就去创建一个单例实例而懒汉式是在真正需要的时候才去创建单例实例

接下来,我们一起来看一下饿汉式和懒汉式的单例模式,并给出一些JDK源码中使用单例模式的代码示例。

2.1 饿汉式

饿汉式大致的图如下:

根据上述类图结构,可以编写类似如下的代码:

代码语言:javascript
复制
public class EagerSingleton {

    private static final EagerSingleton INSTANCE = new EagerSingleton();

    private EagerSingleton() {
    }

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }

}

代码特点:

  • 在Singleton类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用。这个时候,单例类的唯一实例就被创建出来了。
  • 单例类最重要的特点是类的构造函数是私有的,从而外界不能创建出多个实例出来。
  • 在类中创建一个静态方法,始终返回类中的静态实例。这样每次调用静态方法获取实例的时候,始终获取的是同一个实例。

饿汉式在JDK中,比较典型的就是Runtime类:

2.2 懒汉式

懒汉式单例和饿汉式单例的区别在于,懒汉式不是马上创建一个实例,而是在第一次被引用时才去创建单例实例。

懒汉式的类图结构如下:

根据上述类图结构,可以编写类似如下的代码:

代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern;

public class LazySingleton {

  private static LazySingleton INSTANCE = null;
  
    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }

}

代码特点:

当Singleton类被加载的时候,instance并不实例化,当静态方法getInstance()调用的时候才去判断要不要实例化,这个过程是延迟的,直到使用时才实例化,所以叫懒汉式。

懒汉式在JDK中,比较典型的就是Desktop类:

三、懒汉式存在的问题

接下来,我们看下如下懒汉式代码是否能保证创建单例实例的安全性也即是否只会创建只有一个单例

上述代码存在一个问题:

如果多个线程同时调用getInstance()方法,线程A调用的时候instance为null,而线程B调用方的时候instance也是null。这种就可能导致线程A和线程B各自创建了一个Singleton实例,会导致单例失败,创建多余一个实例。

可以简单用如下多线程下调用实例来验证一下:

代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern;

public class SingletonPatternMain {

  public static void main(String[] args) {
    for(int i=0; i<15;i++) {
      Thread t = new Thread() {
        @Override
        public void run() {
          System.out.println(LazySingleton.getInstance().toString());
        }
      };
      t.start();
    }
  }
  
}

某次执行结果,发现产生不止一个实例:

那么,如何保证单例在多线程环境下也是安全的呢?接下来,我们就来看看保证单例安全性的多种实现。

四、保证单例安全性的多种方式

4.1 为getInstance方法加锁 (syncronized)

这种方式和上述提供的JDK中的Desktop类实现一致。

4.2 double check机制

4.3 采用内部类Holder Lazy class

4.4 采用枚举来实现单例

代码语言:javascript
复制
package com.wangmengjun.tutorial.designpattern;

public enum EnumSingleton {

  INSTANCE;

  private EnumSingleton() {
    
  }
  
  public void saySomthing(String name) {
    System.out.println("Hello, " + name);
  }
  
}

直接使用即可。

相信通过上述几个方面的讲解,读者应该对单例模式的实现、懒汉式在多线程下可能出现的问题、以及单例安全性保证的多种方法都有所了解了。

有兴趣的读者可以动手试试,相信你会获取的更多。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、饿汉式和懒汉式
    • 2.1 饿汉式
      • 2.2 懒汉式
      • 三、懒汉式存在的问题
      • 四、保证单例安全性的多种方式
        • 4.1 为getInstance方法加锁 (syncronized)
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档