likes
comments
collection
share

单例模式的6种实现方式

作者站长头像
站长
· 阅读数 74

大家好,我是尚影嫣🌷,一名Java后端程序媛,热爱技术,更热爱分享。如果您喜欢我的文章,欢迎点赞➕关注💖,让我们一起持续学习,持续改进,成为更好的我们~😉

前言

最近在温习设计模式,设计模式就是一种最佳实践方式,它提供了软件开发过程中面临的一般问题的最佳解决方案。我们先来看最简单最常用的单例模式

什么是单例模式

单例模式(Singleton Pattern) 是 Java 中最简单,也是最常用的设计模式之一。

单例模式的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

数学与逻辑学中,将 singleton 定义为 “有且仅有一个元素的集合”

单例模式最早的定义出现在《设计模式》一书中:

“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”

Java中单例模式的定义是:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

单例模式的特性:

  1. 通过单例模式的方法创建的类在当前进程中只有一个实例。
  2. 在类内部创建自己的实例。
  3. 对外提供一个返回其单例对象的方法。

类图如下: 👇

访问类指的是使用单例类对象的类。 单例模式的6种实现方式

单例模式的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;
}

总结

  1. 饿汉式最为简单实用,且线程安全。不要求懒加载的情况下,推荐使用
  2. 登记式-静态内部类的方式实现了懒加载,比饿汉式更节省内存空间;
  3. 懒汉式如果不加锁就无法保证线程安全,如果加锁就会影响效率,较为复杂,不推荐使用
  4. 枚举式比较简洁,线程安全,且防止反序列化,设计上最为完美
转载自:https://juejin.cn/post/7140266949962891277
评论
请登录