概念定义

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)单例。
    • 推荐静态内部类单例。
    • 应避免使用双重校验锁单例。
  • 若无特殊需要,优先使用即时加载模式的单例。
  • 对于一些无状态的具有"唯一"特征的类(如工具类),建议使用静态方法实现。

Java设计模式:Singleton(单例)模式的更多相关文章

  1. Singleton(单例)模式

    Singleton(单例)模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点. public class Singleton { private static Singleton ourIns ...

  2. Java设计模式之单例

    一.Java中的单例: 特点: ① 单例类只有一个实例 ② 单例类必须自己创建自己唯一实例 ③ 单例类必须给所有其他对象提供这一实例 二.两种模式: ①懒汉式单例<线程不安全> 在类加载时 ...

  3. Java设计模式之单例设计模式总结

    package singleton; /**单例设计模式 饿汉式 * * @author gx *这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化, ...

  4. Java设计模式之 — 单例(Singleton)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/8860649 写软件的时候经常需要用到打印日志功能,可以帮助你调试和定位问题,项目上 ...

  5. [译]Java 设计模式之单例

    (文章翻译自Java Design Pattern: Singleton) 单例是在Java最经常被用到的设计模式.它通过阻止其他的实例化和修改来用于控制创建对象的数目.这一特性可应用于那些当只有一个 ...

  6. 从别人写的 Object-C 中 Singleton (单例) 模式 中的一些理解--备

    关于 面向对象的设计模式 对于面向对象的设计模式,想必大家并不陌生吧. 纵观23种设计模式中,数单例模式(Singleton)和工厂模式(Factory Method)最为熟悉和基础吧.当然,本文总结 ...

  7. JAVA设计模式:单例设计

    1.单例设计Singleton的引出 单例设计,从名字上首先可以看出单---即只有一个,例---只的是实例化对象:那么单例也就是说一个类,只产生了一个实例化对象.但是我们都知道,一个类要产生实例化对象 ...

  8. C++ Singleton (单例) 模式最优实现

    参考:http://blog.yangyubo.com/2009/06/04/best-cpp-singleton-pattern/ 索引 静态化并不是单例 (Singleton) 模式 饿汉模式 懒 ...

  9. java设计模式之单例设计模式

    单例设计模式 保证一个类在使用过程中,只有一个实例.优势就是他的作用,这个类永远只有一个实例. 优势:这个类永远只有一个实例,占用内存少,有利于Java垃圾回收. 单例设计模式关键点 私有的构造方法. ...

随机推荐

  1. 百度云盘资源 for MAC 第三方工具不限速下载

    相信大家都比较困惑,百度网盘客户端限速后一般只有几十K的下载速度,Windows有百度网盘破解版,但MAC的破解版似乎不存在,要提速的话,一般的做法是开超级会员(27元/月),身为程序员的我们,是不是 ...

  2. Asia Yokohama Regional Contest 2018 G题 What Goes Up Must Come Down

    链接 G题 https://codeforces.com/gym/102082 使其成为单峰序列需要交换多少次相邻的数. 树状数组维护逆序对. 对于每个序列中的数,要么在单峰的左侧,要么在单峰的右侧, ...

  3. vue安装遇到的5个报错小结

    前言 这篇博文不会教你怎么安装vue,但会告知安装过程中可能遇到的5个问题 2017年我写过一篇安装vue的博客,详情:https://www.cnblogs.com/tu-0718/p/752109 ...

  4. Selenium(五):CSS选择器(二)

    1. CSS选择器 1.1 选择语法联合使用 CSS selector的另一个强大之处在于:选择语法可以联合使用. html代码: <div id='bottom'> <div cl ...

  5. javaWeb核心技术第十四篇之easyui

    网站是分为网站的前台和网站的后台. 前台--给用户看的 例如:商城 后台--给管理员看的 例如:商城后台 目的:用来添加维护数据 BootStrap:jsp 页面显示,效果好,美观,适合作为用户界面. ...

  6. Add an Item to the Navigation Control 将项目添加到导航控件

    In this lesson, you will learn how to add an item to the navigation control. For this purpose, the N ...

  7. 纯css实现checkbox样式改变

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...

  8. NumPy数据的归一化

    数据的归一化 首先我们来看看归一化的概念: 数据的标准化(normalization)和归一化 数据的标准化(normalization)是将数据按比例缩放,使之落入一个小的特定区间.在某些比较和评价 ...

  9. tomcat7控制台日志中文乱码

    windows电脑 idea启动Tomcat调试程序时,Tomcat控制台输出里,中文是乱码. 解决办法: 修改Tomcat/bin/catalina.bat文件: set JAVA_OPTS= 的内 ...

  10. Nuget使用时遇到的问题,Solved

    在VS的程序包管理控制台中输入Install-package MySql.Data时,默认安装最新的版本8.0.18, 但是安装完成后,发现包并没有添加到项目的引用列表中, 在解决方案的package ...