面试:请手写一个单例模式

你好,我是风一样的树懒,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。

点击下方👇关注公众号,带你一起复习后端技术,看看面试考点,补充积累技术知识,每天都为面试准备积累


文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读


手写一个单例模式是面试中常见的题目之一。下面是单例模式的不同实现方式,包括 懒汉式、饿汉式、双重检查锁(DCL) 和 枚举类型 的实现。每种方式的实现有其适用场景和优缺点。



01
饿汉式单例


饿汉式是最简单的单例实现,类在加载时就创建实例,线程安全,不会出现多线程问题,但如果实例非常重,且并不一定会使用到,可能会浪费资源。

public class Singleton {    // 创建一个静态常量来保存实例    private static final Singleton instance = new Singleton();    // 私有化构造方法,防止外部直接创建实例    private Singleton() {}    // 提供一个公共的静态方法来访问单例    public static Singleton getInstance() {        return instance;    }}


02
懒汉式单例


懒汉式单例在第一次使用时才会创建实例。为了确保线程安全,通常会使用 synchronized 或 volatile。

2.1 线程不安全的懒汉式

public class Singleton {    private static Singleton instance;    private Singleton() {}    public static Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

这种方式存在线程安全问题,多个线程可能会在 instance 为 null 时同时创建多个实例。


2.2 线程安全的懒汉式

通过 synchronized 修饰 getInstance() 方法来确保线程安全,但每次调用时都会加锁,性能较差。

public class Singleton {    private static Singleton instance;    private Singleton() {}    public static synchronized Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}


03
双重检查锁(DCL)单例


双重检查锁定(Double-Checked Locking)是懒汉式的优化版本,先检查实例是否为 null,若为 null,才加锁创建实例。在加锁后,再检查一次实例是否已经创建,避免重复加锁。

public class Singleton {    private static volatile Singleton instance;    private Singleton() {}    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}
  • volatile 保证实例的可见性,防止指令重排,确保在多线程下的正确性。

  • 双重检查减少了不必要的同步锁,提高了性能。


04
静态内部类单例


静态内部类单例是实现单例模式的推荐方式。它结合了懒加载和线程安全的优点,并且通过 JVM 的类加载机制来确保实例的唯一性。

public class Singleton {    // 静态内部类    private static class SingletonHelper {        // 静态初始化器,在类加载时创建实例        private static final Singleton instance = new Singleton();    }    private Singleton() {}    public static Singleton getInstance() {        return SingletonHelper.instance;    }}

为什么线程安全:静态内部类在被加载时,JVM 会确保它的线程安全,只有在第一次调用 getInstance() 时,SingletonHelper 才会被加载,因此线程安全且延迟加载。

优点:避免了双重检查锁定的复杂性,且具备懒加载的特点。


05
枚举类型单例


在 Java 中,枚举类型是创建单例的最推荐方式,JVM 会自动保证枚举实例的唯一性和线程安全。

public enum Singleton {    INSTANCE;    // 其他方法    public void someMethod() {        System.out.println("Singleton method");    }}

为什么线程安全:JVM 在加载枚举时会确保实例的唯一性,并且它是线程安全的。避免了其他实现方式可能出现的问题(如反射或序列化攻击)。

优点:实现简单,避免了反射、序列化等攻击方式。


06
总结


饿汉式:简单易懂,线程安全,缺点是即使不需要该实例,JVM 也会在启动时创建。

懒汉式:延迟加载,但需要注意线程安全问题,synchronized 会影响性能。

双重检查锁:解决了懒汉式性能问题,但实现较为复杂。

静态内部类:是目前最推荐的单例实现方式,线程安全且懒加载。

枚举:最安全且最简洁的单例实现,JVM 会确保枚举实例的唯一性和线程安全。

如果面试中让你手写,你会写哪一个?

我可能会从第二个里面挑一个写,因为这样,面试官可能会继续问题,然后再引申到写第三个。


今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!

END


扫码关注

一起积累后端知识
不积跬步,无以至千里
不积小流,无以成江海

喜欢此内容的人还喜欢

《Java面试题指南》回归啦~


一个阿里二面面试官必问的问题


Lambda表达式说爱你不容易


分享面试:mysql数据库索引失效的情况


Spring-Boot中一个不起眼的好工具StopWatch