概念定义

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. springboot+quartz+数据库存储

    Spring整合Quartz a.quartz调度框架是有内置表的 进入quartz的官网http://www.quartz-scheduler.org/,点击Downloads, 下载后在目录\do ...

  2. 你真的理解Java 注解吗?

    你真的理解Java 注解吗? 1.什么是注解? 官方解释: Java 注解用于为 Java 代码提供元数据.作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的.Java ...

  3. STM32基本GPIO操作:点灯(库函数+寄存器)

    社团作业=_= 开发版上的LED灯负极连接在PB5口,正极串联一510Ω电阻后与3.3V相连 若开发板不带LED灯则需要自行连接,务必串联一个合适的电阻防止LED灯烧坏 零.一个有趣的延时函数 来自于 ...

  4. 第04组 Beta冲刺(1/4)

    队名:斗地组 组长博客:地址 作业博客:Beta冲刺(1/4) 各组员情况 林涛(组长) 过去两天完成了哪些任务: 1.分配展示任务 2.收集各个组员的进度 3.写博客 展示GitHub当日代码/文档 ...

  5. React: React组件的生命周期

    一.简介 在前面的第二篇博文中对组件的生命周期虽然做了一个大略介绍,但总感觉说的过于简单,毕竟生命周期是React组件的核心部分.在我们熟练使用React挂载和合成组件来创建应用表现层的过程中,针对数 ...

  6. Vue 从入门到进阶之路(十一)

    之前的文章我们说了一下 vue 中组件的原生事件绑定,本章我们来所以下 vue 中的插槽使用. <!DOCTYPE html> <html lang="en"&g ...

  7. (十六)c#Winform自定义控件-文本框哪里去了?-HZHControls

    官网 http://www.hzhcontrols.com 前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. GitHub:https://github.com/kww ...

  8. 我来告诉你:VS2019开发ASP.NET Core 3.0 Web项目,修改视图后,刷新浏览器看不到修改后的效果怎么处理

    VisualStudio2019下一个2.2另一个3.0页面修改如下,但是3.0刷新没有任何变化,难道VS以后不能做前端开发了?大家可能没有看官方文档 根据文章所说你需要: 1.安装 Microsof ...

  9. javascript 模块化开发(一)

    什么是模块化 将一组模块(及其依赖项)以正确的顺序拼接到一个文件(或一组文件)中的过程. 传统的模块化做法. 模块是实现特定功能的一组属性和方法的封装. 将模块写成一个对象,所有的模块成员都放到这个对 ...

  10. Flutter Text文本

    import 'package:flutter/material.dart'; void main() { runApp( App() ); } class App extends Stateless ...