博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
对几种单例模式写法的理解
阅读量:6656 次
发布时间:2019-06-25

本文共 3711 字,大约阅读时间需要 12 分钟。

前言

单例模式是设计模式中非常基础的一种模式,有多种写法。本文主要分析常见的几种写法的优缺点进行简单的分析和说明。

单例模式,顾名思义就是这个类只有一个实例,并且只具有私有的构造器,外部无法通过类的构造器来创建实例。

常见的五种写法

1. 饿汉式

public class Singleton {    private static final Singleton INSTANCE = new Singleton();    private Singleton() {    }    public static Singleton getInstance() {        return INSTANCE;    }}复制代码

饿汉式的写法,getInstance() 方法不需要加同步关键字 synchronized,因为实例已经在类加载的时候已经创建好了,所有的线程调用 getInstance() 方法,都拿到的是同一个实例。

2. 懒汉式

public class Singleton {    private static Singleton INSTANCE;    private Singleton() {    }    public synchronized static Singleton getInstance() {        if (INSTANCE == null) {            INSTANCE = new Singleton();        }        return INSTANCE;    }}复制代码

懒汉式的写法,getInstance 方法需要加同步关键字 synchronized,确保同一时刻只有一个线程进入 getInstance 方法代码块。

懒汉式的写法相当于延迟创建实例。有一个缺点,客户端每次调用 getInstance 方法时都做了同步,即便 INSTANCE 已经不为 null 了,导致会有性能损耗。

3. 双重检查加锁

public class Singleton {    private volatile static Singleton INSTANCE; //volatile 关键字    private Singleton() {    }    public static Singleton getInstance() {        if (INSTANCE == null) { //第一次检查            synchronized (Singleton.class) {                if (INSTANCE == null) { //第二次检查                    INSTANCE = new Singleton();                }            }        }        return INSTANCE;    }}复制代码

双重检查加锁的版本,可以理解为懒汉式写法上的一种改进。解决了懒汉式每次调用getInstance 方法都需要同步的缺点,性能上会比懒汉式有更好的表现。

有3个地方需要注意:

  • 第一次检查,当实例被创建后,即 INSTANCE != null时,调用 getInstance 会直接返回单实例,没有做同步,这就是相比懒汉式写法优化的点。
  • 第二次检查是必须的,假如没有第二次检查,两个线程 A, B 同时通过第一次检查后,到达同步块外边,线程 A 拿到同步锁创建实例,释放锁;接着,线程 B 拿到同步锁,再次创建一个实例。就创建了不止一个实例,违背了单例的原则。
  • volatile 关键字修饰 INSTANCE 静态域。加了这个关键字后,能够保证调用 getInstance 方法时,各个线程里面 INSTANCE 的状态保持同步。例如线程 A 设置了 INSTANCE 的值,那线程 B 马上就会知道,避免了状态不同步导致创建多个实例的可能。

4. 静态内部类

public class Singleton {    private Singleton() {    }    public static Singleton getInstance() {        return Holder.INSTANCE;    }    private static class Holder {        private static final Singleton INSTANCE = new Singleton();    }}复制代码

第一次调用getInstance方法时,Holder.INSTANCE第一次被读取,静态内部类Holder得到初始化,Holder下的静态域被初始化。只会在 JVM 装载Holder类的时候初始化一次,并由 JVM 来保证它的线程安全。

这里线程安全是通过 JVM 的类装载机制保证的,所以在 getInstance 方法定义时不需要添加synchronized 关键字。

5. 枚举

public enum  Singleton {    INSTANCE;    public void method() {}}复制代码

枚举单例实现相比双重检查加锁和静态内部类的实现,要简洁很多。双重检查加锁需要保证线程安全,所以很多代码都是在处理同步问题。

静态内部类的实现,新增了一个静态内部类,利用 JVM 的类装载机制来保证线程安全,代码量上也会有所增加。

而枚举单例的线程安全不需要我们关心,根本上也是利用了 JVM 的类装载来保证线程安全。

枚举实现原理

通过 javac 编译 Singleton.java 文件,生成了一个 Singleton.class 文件,再通过 jad 反编译工具对 Singleton.class 文件进行反编译,反编译后的代码如下:

public final class Singleton extends Enum{    public static Singleton[] values()    {        return (Singleton[])$VALUES.clone();    }    public static Singleton valueOf(String s)    {        return (Singleton)Enum.valueOf(test/Singleton, s);    }    private Singleton(String s, int i)    {        super(s, i);    }    public void method()    {    }    public static final Singleton INSTANCE;    private static final Singleton $VALUES[];    static     {        INSTANCE = new Singleton("INSTANCE", 0);        $VALUES = (new Singleton[] {            INSTANCE        });    }}复制代码

通过上面的反编译代码可知,枚举类型编译后,会生成一个继承自 Enum 的类,并且枚举的单实例被编译成了一个静态域,在这个类的 static 块里面初始化这个静态域。

static 块会在类第一次被加载的时候执行,而 JVM 装载类是线程安全的,所以枚举单例根本上还是利用了 JVM 的类装载机制来保证线程安全,跟静态内部类的机制有点相似。但枚举写法的代码量上要少很多。

总结

单例模式有多种写法,围绕的核心问题就是怎样解决线程安全和性能问题,保证单例的特性,并且在性能有良好的表现。

  • 饿汉式写法实例初始化太早,在不需要的时候也会进行实例化,可能导致资源消耗;

  • 懒汉式写法延迟加载了实例,但每次获取实例时,都要进行同步,性能上会有消耗;

  • 双重检查加锁写法是对懒汉式的改良,只有通过第一重检查后,才会进行同步。如果实例已经被创建,后面再调用获取实例方法,不会再进入同步块。代码量相对比较多。

  • 静态内部类写法是利用了 JVM 装载类机制的线程安全特性,所以我们不需要处理同步相关的逻辑,JVM 已经帮我们处理好了。代码量相对比较多。

  • 枚举单例底层也是利用了 JVM 装载类是线程安全特性,代码量非常少。另外,对于类实现 Serializable 情况,枚举的反序列化不是通过反射实现,所以也就不会发生由反序列化导致的单例破坏问题。

对于实现序列化和反射破坏单例的情况,本文不涉及,感兴趣的同学可以找找相关资料。

转载于:https://juejin.im/post/5c516f2951882525da2677e5

你可能感兴趣的文章
elasticsearch
查看>>
eclipse 插件管理和使用
查看>>
.Net 分布式云平台基础服务建设说明概要
查看>>
读《暗时间》一书,暗时间的8个方面和3种应对方法
查看>>
platform_driver_register( )过程追踪
查看>>
m0n0wall安装配置
查看>>
双向链表
查看>>
一生的诠释改变你的一生
查看>>
WebInterface / Storefront访问加速
查看>>
centos6-5安装和配置cobbler-2-6实现自动化无人値守网络批量安装
查看>>
mysql基本命令之增删改查
查看>>
puppet 简单使用
查看>>
Laravel 5.2 教程 - 邮件
查看>>
Linux SSH批量分发管理
查看>>
指定域控制器登录
查看>>
10 alternative careers for burned-out IT workers
查看>>
我的友情链接
查看>>
AngularJS第四课:应用模块化
查看>>
《模式 工程化实现及扩展 (设计模式 C#版)》 - 书摘精要
查看>>
Spring Boot 配置文件 – 在坑中实践
查看>>