Java设计模式:Singleton(单例)模式

Wesley13
• 阅读 518

概念定义

Singleton(单例)模式是指在程序运行期间, 某些类只实例化一次,创建一个全局唯一对象。因此,单例类只能有一个实例,且必须自己创建自己的这个唯一实例,并对外提供访问该实例的方式。 单例模式主要是为了避免创建多个实例造成的资源浪费,以及多个实例多次调用容易导致结果出现不一致等问题。例如,一个系统只能有一个窗口管理器或文件系统,一个程序只需要一份全局配置信息。

应用场景

  • 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如缓存、日志对象、应用配置。
  • 控制资源的情况下,方便资源之间的互相通信。如数据库连接池、线程池等。

单例实现

根据加载的时机可以分为即时加载延时加载两种模式。

即时加载

在单例类被加载时就创建单例的方式,称为即时加载单例(也称饿汉式)。

枚举类单例(推荐方式)

示例代码如下:

public enum EnumSingleton {
    INSTANCE;
    public static EnumSingleton getInstance() { // 照顾开发者旧有习惯
        return INSTANCE;
    }

    // 外部可调用EnumSingleton.INSTANCE.doSomething()或EnumSingleton.getInstance().doSomething()
    public void doSomething() {
        System.out.println("EnumSingleton: do something like accessing resources");
    }
}

此类单例具有以下优点:

  • 简洁高效
  • 实例是静态的,线程安全
  • 不存在clone、反射、序列化破坏单例问题

缺点则有:

  • 枚举单例不能继承和被继承
  • 可读性稍低(主要因为此方式较为"新颖")

静态公有域单例

示例代码如下:

public class StaticFieldSingleton {
    public static final StaticFieldSingleton INSTANCE = new StaticFieldSingleton();
    private StaticFieldSingleton() { // 私有化构造方法,防止外部实例化而破坏单例
        if (INSTANCE != null) { // 防止反射攻击
            throw new UnsupportedOperationException();
        }
    }

    // 外部可调用StaticFieldSingleton.INSTANCE.doSomething()
    public void doSomething() {
        System.out.println("StaticFieldSingleton: do something like accessing resources");
    }
}

静态工厂方法单例

示例代码如下:

public class StaticMethodSingleton {
    private static final StaticMethodSingleton INSTANCE = new StaticMethodSingleton(); // INSTANCE由private修饰
    private StaticMethodSingleton() {
        if (INSTANCE != null) { // 防止反射攻击
            throw new UnsupportedOperationException();
        }
    }
    public static StaticMethodSingleton getInstance() {
        return INSTANCE;
    }

    // 外部可调用StaticFieldSingleton.getInstance().doSomething()
    public void doSomething() {
        System.out.println("StaticMethodSingleton: do something like accessing resources");
    }
}

静态工厂方法比静态公有域单例更具灵活性:

  • 内部可以改变单例实现方式,例如将即时加载改造成延时加载/懒加载,保持API不变。
  • 甚至可以改变类是否是单例。例如业务场景有所改变,将原先的单例变成非单例,也能保持API不变。

延时加载

即时加载相对简单,作为主要推荐的单例模式。但在有些业务场景中,不希望单例被过早创建,而在真正使用的那刻才创建,即延时加载单例(也称懒汉式)。此类场景有:

  • 创建实例的开销很大,但访问频率却很低
  • 单例的创建依赖于其他资源的创建,为保证数据完整性必须延迟创建。
  • ...

静态内部类单例(推荐方式)

示例代码如下:

public class StaticHolderSingleton {
    private static class SingletonHolder {
        private static final StaticHolderSingleton INSTANCE = new StaticHolderSingleton();
    }
    private StaticHolderSingleton() {}
    public static StaticHolderSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // 外部可调用StaticHolderSingleton.getInstance().doSomething()
    public void doSomething() {
        System.out.println("StaticHolderSingleton: do something like accessing resources");
    }
}

静态内部类单例有以下特点:

  • 只有当getInstance()方法被外部首次调用时,SingletonHolder类才被JVM加载和初始化,静态属性INSTANCE也跟着被初始化,从而达到延迟加载的目的。
  • JVM保证初始化SingletonHolder类时,具有线程安全性,因此不会增加任何性能成本和空间浪费。

双重校验锁(DCL)单例

示例代码如下:

public class DCLSingleton {
    private static volatile DCLSingleton instance; // volatile禁止指令重排序,并保证内存可见性
    private DCLSingleton() {}
    public static DCLSingleton getInstance() {
        if (instance == null) { // 此处判空旨在提高性能
            synchronized (DCLSingleton.class) {
                if (instance == null) {
                    instance = new DCLSingleton();
                }
            }
        }
        return instance;
    }

    // 外部可调用DCLSingleton.getInstance().doSomething()
    public void doSomething() {
        System.out.println("DCLSingleton: do something like accessing resources");
    }
}

DCL单例比较复杂,而且用到synchronized和volatile,性能有所损失。

破坏单例模式的方法

Java对象可通过new、克隆(clone)、反序列化(serialize)、反射(reflect)等方式创建。 通过私有化或不提供构造方法,可阻止外部通过new创建单例实例。其他几种创建方式则需要特别注意(枚举单例不存在本节风险)。

克隆

java.lang.Obeject#clone()方法不会调用构造方法,而是直接从内存中拷贝内存区域。因此,单例类不能实现Cloneable接口。

反射

反射通过调用构造方法生成新的对象,可在构造方法中进行判断,实例已创建时抛出异常,如StaticFieldSingleton所示。

反序列化

普通Java类反序列化时会通过反射调用类的默认构造方法来初始化对象。如果单例类实现java.io.Serializable接口, 就可以通过反序列化破坏单例。 因此,单例类尽量不要实现序列化接口。如若必须,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象:

public Object readResolve() {
    return instance;
}

单例 vs 静态方法

单例:在一个JVM中只允许一个实例存在。单例常常是带有状态的,可以携带更丰富的信息,使用场景更加广泛。

  • 单例是面向对象的
  • 有状态的
  • 方法跟实例是相关的
  • 人为保证线程安全
  • 能实现接口或者继承一个超类

静态方法: 对于不需要维护任何状态,仅提供全局访问方法的类,可将其实现为更简单的静态方法类(如各种Uitls工具类),它的速度更快。

  • 静态方法是面向过程的
  • 无状态的
  • 方法跟实例是无关的
  • 天然线程安全
  • 静态方法速度更快(其绑定在编译期就进行)

业界实践

  • java.lang.Runtime.getRuntime(JDK)
  • java.util.concurrent.TimeUnit(JDK)
  • 无数开源软件

要点总结

  • 单例模式按加载时机可分为即时加载延时加载两种方式。
  • 即时加载有:枚举类单例、静态公有域单例和静态工厂方法单例。
    • 推荐程度: 枚举类单例 > 静态工厂方法单例 > 静态公有域单例。
    • 特例:若单例类必须要继承某个超类,则不宜使用枚举类单例。
  • 延时加载有:静态内部类单例和双重校验锁(DCL)单例。
    • 推荐静态内部类单例。
    • 应避免使用双重校验锁单例。
  • 若无特殊需要,优先使用即时加载模式的单例。
  • 对于一些无状态的具有"唯一"特征的类(如工具类),建议使用静态方法实现。
点赞
收藏
评论区
推荐文章
3A网络 3A网络
1年前
Golang 常见设计模式之单例模式
之前我们已经看过了Golang常见设计模式中的装饰和选项模式,今天要看的是Golang设计模式里最简单的单例模式。单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在。根据这一特性,我们可以将其应用到全局唯一性配置、数据库连接对象、文件访问对象等。Go语言实现单例模式的方法有很多种,下面我们就一起来看一下。饿汉式饿汉式实现单例模式非
Wesley13 Wesley13
2年前
java设计模式1
1:单例模式简介  单例模式是一种常用的软件设计模式,它确保某个类只有一个实例,而且自行实例化并向整个系统提供唯一的实例。总而言之就是在系统中只会存在一个对象,其中的数据是共享的  特点:    单例类只能有一个实例,所以一般会用static进行修释。    单例类必须自己创建自己的唯一实例。也就是在类中要new一个自己。    单例类必
Wesley13 Wesley13
2年前
java 23种设计模式(五、单例模式)
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。单例模式的结构  单例模式的特点:单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。  饿汉式单例类publicclassEagerSingleton
红烧土豆泥 红烧土豆泥
3年前
创建型模式之单例设计模式
什么是单例设计模式?顾名思义,只有一个实例。单例模式它主要是确保一个类只有一个实例,并且可以提供一个全局的访问点。废话少说,直接上干货了单例模式之饿汉式所谓饿汉式,顾名思义,“它很饿”。所以说,它一旦被加载进来,就会直接实例化一个对象。例如:languageclassSingleton{privatestaticfin
Wesley13 Wesley13
2年前
JAVA设计模式之单例设计模式
    单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。  在JAVA中实现单例,必须了解JAVA内存机制,JAVA中实例对象存在于堆内存中,若要实现单例,必须满足两个条件:  1.限制类实例化对象。即只能产生一个对象。
Wesley13 Wesley13
2年前
Java单例模式
什么是单例模式  单例模式是在程序中,一个类保证只有一个实例,并提供统一的访问入口。为什么要用单例模式节省内存节省计算如对象实例中的一样的,那就不用每次都创建一个对象方便管理因为单例提供一个统一的访问入口,不需要创建N多个对象,很多工具类都用了单例实现,如日志、字符串工具类
Wesley13 Wesley13
2年前
PHP单例模式(精讲)
首先我们要明确单例模式这个概念,那么什么是单例模式呢?单例模式顾名思义,就是只有一个实例。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类我们称之为单例类。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例
Stella981 Stella981
2年前
C#设计模式(1)——单例模式(Singleton)
单例模式即所谓的一个类只能有一个实例,也就是类只能在内部实例一次,然后提供这一实例,外部无法对此类实例化。单例模式的特点:1、只能有一个实例;2、只能自己创建自己的唯一实例;3、必须给所有其他的对象提供这一实例。普通单例模式(没有考虑线程安全)  ///<summary///单例模式
Wesley13 Wesley13
2年前
(面试常问)4种单例设计模式的总结(内含代码以及分析)
单例设计模式:  单例模式,是一种常见的软件设计模式.在它的核心结构中只包含了一个被称为单例的特殊类.通过单例模式可以保证系统中只有该类的一个实例对象.优点:  实例控制:单例模式会阻止其它对象实例化其自己的单例对象的副本,从而确保所有对象都访问的是唯一的实例   灵活性:因为类控制了实例化过程,所以类可以很灵活的更改实
Wesley13 Wesley13
2年前
23种设计模式(1):单例模式
定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。类型:创建类模式类图:!23种设计模式(1):单例模式第1张|快课网(http://static.oschina.net/uploads/img/201407/05200605_0dij.gif"23种设计模式(1):单例模式