单例模式在 Java 中的几种实现

简介: 前言经典的设计模式有23种,分为创建型、结构型、行为型,分别适用于不同的场景。由于设计模式过多,很难一篇文章就讲清楚,因此后面的文章会将常见的设计模式做一个拆分的介绍。

前言


经典的设计模式有23种,分为创建型、结构型、行为型,分别适用于不同的场景。由于设计模式过多,很难一篇文章就讲清楚,因此后面的文章会将常见的设计模式做一个拆分的介绍。


什么是单例模式?


一个类只允许创建一个实例,这个类就叫做单例类,这种设计模式就叫做单例模式。单例的范围包括线程内唯一、进程内唯一、集群内唯一。通常情况我们所说的单例范围是指进程内的单例。在我们常用的 Spring 框架中,甚至实现了容器范围内的单例。


在 Java 中,类由类加载器 ClassLoader 加载到 JVM 中,通常情况下,JVM 中的 ClassLoader 使用双亲委派模型不会重复加载相同的类,而如果使用多个自定义的 ClassLoader 加载同一个类,则每个 ClassLoader 都会加载一份类到 JVM 中。这也就导致了在 Java 中线程或进程内的单例必须限定在 ClassLoader 范围内。


什么情况下可以使用单例模式?


有些数据在系统中只能保存一份,可以设计为单例,如配置类;

有些对象是无状态的也可以设计为单例,此时类似于静态类,可以减少内存占用;

如何实现一个单例模式?

单例具有自己的适用范围,不同的范围有不同的实现方式,下面按照从小到大的范围进行介绍。


线程范围内的单例模式


线程范围内的单例模式需要保证同一个类在某个线程中只有一份实例。很自然的,我们可以将线程 ID 作为 key,单例对象作为 value 存入 Map 中,这样就保证了每个线程对应一个单例对象。在 Java 中,我们可以使用 ThreadLocal 类,其实现方式和我们所述一致。用代码的形式实现线程范围内的单例模式如下。


public class Singleton {
    private static final ThreadLocal<Singleton> THREAD_LOCAL = ThreadLocal.withInitial(Singleton::new);
    private Singleton() {
    }
    public static Singleton getInstance() {
        return THREAD_LOCAL.get();
    }
}


进程范围内的单例模式


在 Java 中,进程内的单例模式有多种实现方式,包括饿汉式、懒汉式、双重检测、静态内部类、枚举。


饿汉式单例模式实现


“饿汉式”、“懒汉式”词语来源已无从考证,不过用这两个词语来形容其实现方式确实比较恰当。“饿汉式”表示比较饥饿,迫切的想要吃东西,对应到单例模式实现则是说希望尽快创建单例对象。代码实现如下。


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


饿汉式单例模式在类被加载到 JVM 时就会创建实例,由 JVM 保证不会在多线程下创建多个类的实例,如果类的实例化比较耗时,将会降低程序的启动速度、提前占用内存。不过这种实现的思想很像 fail-fast,如果有异常就尽快抛出,并且实现方式也比较简单,因此不失为是一种好的实现方式,个人也比较喜欢这种方式。


懒汉式实现单例模式


“懒汉式”表示实例化单例对象是懒惰的、延迟的,只有使用的时候才会创建单例对象。用代码表示如下。


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


懒汉式实现单例模式,为了避免多线程下同时调用获取单例的方法导致创建多个对象,在方法上加了类级别的锁,由于锁的粒度较大,因此可能会产生性能问题。多线程编程提高性能的一个方法就是降低锁的粒度,因此又诞生了双重检测的方法。


双重监测实现单例模式


双重监测实现单例模式第一次判断单例对象是否已创建时并不加锁,只有当单例对象不存在时才加锁再次判断对象是否存在以决定是否需要创建。对应代码如下。


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;
    }
}


细心的同学可能会发现这个版本的代码,在 INSTANCE 变量中添加了 volatile 关键字。


这是因为实例化单例和赋值单例对象给 INSTANCE 变量是两个操作,由于指令重排序,可能刚为新创建的对象分配好内存还未创建对象结束就把这个内存的地址赋值给 INSTANCE 变量,然后方法返回这就导致 INSTANCE 变量对应的对象还是 null ,多线程下仍可能出现多次创建单例对象的情况。


通过为 INSTANCE 添加 volatile 关键词禁止指令重排序可以解决这个问题。不过在高版本的 JDK 中已经解决了这个问题,原理就是将多个操作合并为一个原子操作。


静态内部类实现单例模式


饿汉式单例模式在类加载到 JVM 时就会创建单例对象,为了延迟创建单例对象,还可以利用静态内部类的特性。静态内部类在外部类初始化时并不会初始化,也就达到了延迟创建单例对象的目的。转换为代码如下。


public class Singleton {
    private static class SingletonHolder {
        private static Singleton INSTANCE = new Singleton();
    }
    private Singleton() {
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}


这种实现方式也比较简洁,如果一定要延迟初始化单例对象的话推荐使用这种方式。


枚举实现单例模式


和饿汉式、静态内部类相似,通过枚举实现单例模式也是借助了 JVM 保证多线程下不会创建多个单例对象。使用枚举实现单例模式的代码如下。


public enum Singleton {
    INSTANCE;
    public static Singleton getInstance() {
        return INSTANCE;
    }
}


集群范围内的单例模式


集群范围内的单例模式,是说在整个集群中一个类只有同时只能有一个实例。由于每个进程都可以创建类的实例,因此这需要分布式锁来保证,同时类需要支持序列化,以便将单例对象在网络中传输并反序列化,通常情况下应该没有需求需要这样做。示例代码如下。


public class Singleton {
    private static DistLock lock = new DistLock();
    private static ObjectStorage storage = new ObjectStorage();
    private static Singleton INSTANCE;
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            try {
                lock.tryLock();
                INSTANCE = storage.load(Singleton.class);
            } finally {
                lock.unLock();
            }
        }
        return INSTANCE;
    }
    public synchronized void freeInstance() {
        storage.save(this, Singleton.class);
        INSTANCE = null;
        lock.unLock();
    }
}


单例模式有何弊端


尽管使用单例模式可以实现全局范围内只有一个实例的目的,很多人都在使用,不过也有人把它称为反模式,这是因为其自身仍然存在一些缺陷。


单例模式会隐藏类之间的依赖关系。直接在方法中使用类似Singleton.getInstance().invoke(..)的代码,阅读者不能一眼看成某个类依赖了单例对象。

单例模式对OOP不够友好,通常情况单例类只有一种实现并没有什么问题,而如果单例类有不同的实现,则需要对单例类做较大的改造。

单例模式对扩展性不够友好,不支持创建多个实例,如果未来需求变化需要创建多个实例则不能满足,这是单例的优点也是单例的缺点。

另外单例模式还不支持参数、由于无法做到依赖注入因此做测试时无法mock单例对象。


总结

单例模式理解相对简单,即全局范围内只有一个实例,不同的范围具有不同的实现方式。它虽然可以保证只能有一个对象,但同时也带来了一系列的问题。


尽管单例模式有不足的地方,但单例模式并非一无是处,例如 Spring 支持 IOC 容器内的单例,巧妙的解决了上述的问题。如果非 Spring 环境我们也可以使用工厂模式创建单例对象,同时还方便了以后的扩展。是否使用单例模式这种问题可以说仁者见仁智者见智,如果能接受其弊端则可以使用,否则改用工厂模式或使用 Spring 保证单例即可。


目录
相关文章
|
18天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
25 2
|
29天前
|
设计模式 安全 Java
Java编程中的单例模式深入剖析
【10月更文挑战第21天】在Java的世界里,单例模式是设计模式中一个常见而又强大的存在。它确保了一个类只有一个实例,并提供一个全局访问点。本文将深入探讨如何正确实现单例模式,包括常见的实现方式、优缺点分析以及最佳实践,同时也会通过实际代码示例来加深理解。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的见解和技巧。
95 65
|
18天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
26天前
|
设计模式 SQL 安全
Java编程中的单例模式深入解析
【10月更文挑战第24天】在软件工程中,单例模式是设计模式的一种,它确保一个类只有一个实例,并提供一个全局访问点。本文将探讨如何在Java中使用单例模式,并分析其优缺点以及适用场景。
13 0
|
30天前
|
SQL 设计模式 Java
[Java]单例模式
本文介绍了单例模式的概念及其实现方式,包括饿汉式和懒汉式两种形式,并详细探讨了懒汉式中可能出现的线程安全问题及其解决方案,如锁方法、锁代码块和双重检查锁(DCL)。文章通过示例代码帮助读者更好地理解和应用单例模式。
29 0
|
2月前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
42 11
|
1月前
|
设计模式 SQL 安全
【编程进阶知识】Java单例模式深度解析:饿汉式与懒汉式实现技巧
本文深入解析了Java单例模式中的饿汉式和懒汉式实现方法,包括它们的特点、实现代码和适用场景。通过静态常量、枚举类、静态代码块等方式实现饿汉式,通过非线程安全、同步方法、同步代码块、双重检查锁定和静态内部类等方式实现懒汉式。文章还对比了各种实现方式的优缺点,帮助读者在实际项目中做出更好的设计决策。
42 0
|
2月前
|
设计模式 Java 安全
Java设计模式-单例模式(2)
Java设计模式-单例模式(2)
|
3月前
|
设计模式 安全 Java
Java编程中的单例模式深度解析
【8月更文挑战第31天】 单例模式,作为设计模式中的经典之一,在Java编程实践中扮演着重要的角色。本文将通过简洁易懂的语言,逐步引导读者理解单例模式的本质、实现方法及其在实际应用中的重要性。从基础概念出发,到代码示例,再到高级应用,我们将一起探索这一模式如何优雅地解决资源共享和性能优化的问题。
|
3月前
|
设计模式 安全 Java
Java中的单例模式:理解与实践
【8月更文挑战第31天】在软件设计中,单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨Java中实现单例模式的不同方法,包括懒汉式、饿汉式、双重校验锁以及静态内部类等方法。每种方法都有其适用场景和潜在问题,我们将通过代码示例来展示如何根据具体需求选择合适的实现方式。
下一篇
无影云桌面