单例模式的6种实现方式
大家好,我是尚影嫣🌷,一名Java后端程序媛,热爱技术,更热爱分享。如果您喜欢我的文章,欢迎点赞➕关注💖,让我们一起持续学习,持续改进,成为更好的我们~😉
前言
最近在温习设计模式,设计模式就是一种最佳实践方式,它提供了软件开发过程中面临的一般问题的最佳解决方案。我们先来看最简单最常用的单例模式。
什么是单例模式
单例模式(Singleton Pattern) 是 Java 中最简单,也是最常用的设计模式之一。
单例模式的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
数学与逻辑学中,将 singleton 定义为 “有且仅有一个元素的集合”。
单例模式最早的定义出现在《设计模式》一书中:
“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
Java中单例模式的定义是:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
单例模式的特性:
- 通过单例模式的方法创建的类在当前进程中只有一个实例。
 - 在类内部创建自己的实例。
 - 对外提供一个返回其单例对象的方法。
 
类图如下: 👇
访问类指的是使用单例类对象的类。

单例模式的6种实现方式
单例模式的实现方式有懒汉式、饿汉式和枚举。
| 对比 | 懒汉式 | 饿汉式 | 枚举 | 
|---|---|---|---|
| 特点 | 使用时检查是否有无实例,有则返回,没有则进行实例化。 | 类加载到内存后,就实例化出一个单例,由JVM保证线程安全,简单实用。 | 保证线程安全,而且可以防止反序列化。 | 
| 缺点 | 为保证线程安全,需要加上synchronized关键字,会影响程序执行效率。 | 不论用不用,类加载时都会进行实例化,浪费内存空间。 | 较为完美 | 
1、饿汉式-静态变量
类一旦加载就会创建一个单例,保证在调用getInstance()方法之前,单例已经存在了。这种方式比较常用,线程安全,可直接用于多线程场景,但容易产生垃圾对象。
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    /**
     * 构造函数私有化,以保证该类不会被实例化
     */
    private Singleton() { }
    public static Singleton getInstance() {
        return INSTANCE;
    }
 }
2、饿汉式-静态代码块
和第一种方式类似。基于 classloader 机制避免了多线程同步问题。
public class Singleton {
    private static final Singleton INSTANCE;
    //静态代码块
    static {
        INSTANCE = new Singleton();
    }
    /**
     * 构造函数私有化
     */
    private Singleton() { }
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
3、懒汉式-线程安全
在方法上加synchronized关键字,支持多线程,但效率低。
在第一次调用时进行初始化,避免了内存浪费。只能靠加锁(synchronized)来保证单例,加了锁就影响了效率。
public class SingleLazy {
    private static SingleLazy INSTANCE;
    private SingleLazy() { }
    /**
     * 加上synchronized实现线程同步
     */
    public synchronized static SingleLazy getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new SingleLazy();
        }
        return INSTANCE;
    }
}
4、双检锁校验(DCL)
将synchronized加在对象上,提高执行效率,需要加上双重校验。这种方式线程安全,满足懒加载,在多线程情况下也可以保证高性能。
public class SingleLazy {
    /**
     * 加上volatile防止指令重排序
     */
    private static volatile SingleLazy INSTANCE;
    private SingleLazy() { }
    public static SingleLazy getInstance() {
        if (INSTANCE == null) {
            synchronized (SingleLazy.class) {
                //加上双重校验
                if (INSTANCE == null) {
                    INSTANCE = new SingleLazy();
                }
            }
        }
        return INSTANCE;
    }
}
5、登记式-静态内部类
加载外部类时,不加载内部类,实现 lazy loading 懒加载,同饿汉式一样利用了 classloader 机制保证初始化 instance 时只有一个线程,由JVM保证单例。
这种方式也能满足双检锁校验方式的效果,且更简单一些。
只有 getInstance() 方法被显示调用时,才会显示装载 SingleManager 类,实现 instance 的实例化。
public class SingleLazyInner {
    private SingleLazyInner() { }
    /**
     * 静态内部类,外部类加载时不会加载内部类
     */
    private static class SingleManager {
        private static final SingleLazyInner INSTANCE = new SingleLazyInner();
    }
    public static SingleLazyInner getInstance() {
        return SingleManager.INSTANCE;
    }
}
6、枚举
枚举可以说是单例模式的最佳实践,代码非常简洁,保证线程安全,自动支持序列化机制,而且可以防止反序列化。
由于枚举是从 JDK1.5 起才开始引入的特性,所以使用这种方式的人较少。
public enum SingleEnum {
    /**
     * 枚举没有构造方法,可以防止反序列化
     */
    INSTANCE;
}
总结
饿汉式最为简单实用,且线程安全。不要求懒加载的情况下,推荐使用;登记式-静态内部类的方式实现了懒加载,比饿汉式更节省内存空间;懒汉式如果不加锁就无法保证线程安全,如果加锁就会影响效率,较为复杂,不推荐使用;枚举式比较简洁,线程安全,且防止反序列化,设计上最为完美。
转载自:https://juejin.cn/post/7140266949962891277