设计模式之单例模式大全---java实现

发布于:2025-09-09 ⋅ 阅读:(17) ⋅ 点赞:(0)

饿汉式:

概述:

        1.饿汉式是单例模式中最简单、最直接的一种实现方式。

        2.它的核心思想在于:“我饿了,我现在就要吃!” —— 也就是说,不管程序需不需要,我都

           先在类加载的时候就把这个单例实例创建好,等着你来用。

        3.这种“急切”的初始化方式,因此得名“饿汉式”。

代码及注释解释:

就是一上来先创建实例对象,构造器私有,然后通过方法return这个实例对象

public class EagerSingleton {
    
    // 1. 静态常量:在类加载阶段,就由JVM初始化这个唯一实例。
    //    关键字 `final` 可选,但可以明确表示此实例不可改变,增强可读性。
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    
    // 2. 私有构造函数:防止外部通过 `new` 关键字创建实例。
    private EagerSingleton() {
        // 防止在反射攻击下被多次实例化(一种简单的防护,但并非绝对安全)
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("EagerSingleton is initialized!");
    }
    
    // 3. 全局静态访问点:提供给外部获取这个唯一实例的方法。
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

工作原理 

饿汉式的线程安全性并不是通过同步代码(synchronized) 来实现的,而是利用了 JVM 的类加载机制

  • JVM 在加载一个类时,会初始化其静态变量(static)。

  • 这个过程是线程安全的,因为 JVM 保证了每个类只会被加载一次,其静态变量自然也只会被初始化一次。

  • 因此,INSTANCE 这个单例对象在类加载的初始化阶段就被创建出来了,后续所有调用 getInstance() 的方法都只是返回这个早已创建好的对象的引用。

优缺点:

优点
  • 实现简单:代码非常简洁明了,易于理解。

  • 线程安全:无需任何同步措施,由 JVM 底层机制保证线程安全,性能无忧。

 缺点
  • 可能造成资源浪费:这是饿汉式最大的问题。如果这个单例实例的构造过程很耗时(比如加载大量数据、连接资源),或者这个实例在程序运行过程中根本就没被用到,那么它的提前初始化就是一种不必要的内存和计算资源的浪费。

  • 非懒加载 (Lazy Loading):无法做到“用时再创建”,不符合一些特定场景的需求

适用场景

  1. 内存占用小:实例本身占用的内存资源不大,即使不用,也能接受其常驻内存。

  2. 几乎每次运行都会用到:你几乎可以肯定这个实例在程序运行中一定会被使用到。

  3. 追求极致的性能:在超高并发环境下,虽然双重检查锁(DCL)性能也很好,但饿汉式的 getInstance() 方法没有任何同步开销,理论上是最快的。


懒汉式:

.核心概念

        1.懒汉式的核心思想是:“我很懒,只有到你真正需要我的时候,我才会动手创建自己。”

        2.这与饿汉式的“迫不及待”形成鲜明对比。它避免了提前初始化可能造成的资源浪费,只有在

           第一次调用 getInstance() 方法时才会创建实例。

        3.这种“用时再创建”的方式也称为,懒加载,延迟加载 (Lazy Loading)

版本推演以及代码实现

版本1(线程安全问题):

这是最原始的版本,但是面临线程不安全的问题,

假设线程1进入if语句后,还没有执行完创建对象实例的操作

此时线程2也进入了判断就出现了线程冲突

public class UnsafeLazySingleton {
    private static UnsafeLazySingleton instance;

    private UnsafeLazySingleton() {}

    public static UnsafeLazySingleton getInstance() {
        // 线程不安全的关键点!
        if (instance == null) { // 【步骤1】:线程A和线程B同时检查,都发现instance为null
            instance = new UnsafeLazySingleton(); // 【步骤2】:它们都会执行这行代码,导致创建多个实例
        }
        return instance;
    }
}
版本二(解决线程安全 但存在效率问题):

加上关键字后 表明在线程1没执行完时线程2不会进入,会等待

但是这表明,在同一时间内只能有一个线程执行,效率大打折扣,所以也不推荐

public class SynchronizedLazySingleton {
    private static SynchronizedLazySingleton instance;

    private SynchronizedLazySingleton() {}

    // 使用synchronized关键字修饰整个方法
    public static synchronized SynchronizedLazySingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedLazySingleton();
        }
        return instance;
    }
}
版本三(双重检查锁)

此时将关键字放在了最外层判断里面,就算有线程同时进入判断也会先等待,

等前一个创建完后在进行判断,不会重复创建实例,

而其他线程直接在最外层判断被拦截,相比于版本二极大的提高了效率

public class DCLSingleton {
    // 关键:使用volatile关键字禁止指令重排序
    private static volatile DCLSingleton instance;

    private DCLSingleton() {}

    public static DCLSingleton getInstance() {
        // 第一次检查:避免不必要的同步
        if (instance == null) {
            // 同步代码块
            synchronized (DCLSingleton.class) {
                // 第二次检查:确保在同步块内再次判断,防止多次创建
                if (instance == null) {
                    instance = new DCLSingleton(); // volatile 关键字在此处至关重要!
                }
            }
        }
        return instance;
    }
}

注:在声明instance时为什么需要volatile??

  • 语句 instance = new DCLSingleton(); 并不是一个原子操作,它大致分为三步:

    1. 为对象分配内存空间

    2. 初始化对象(调用构造函数等)

    3. 将 instance 引用指向分配的内存地址

  • JVM 可能会进行指令重排序,将步骤2和步骤3调换顺序。如果线程A执行了1和3,但尚未执行2,此时 instance 已不为 null(但对象未初始化)。

  • 此时如果线程B执行第一重检查 if (instance == null),会发现 instance 不为 null,于是直接返回这个半成品对象,从而导致程序出错。

  • volatile 关键字的作用就是禁止JVM对此语句进行指令重排,从而保证线程安全。

版本四(静态内部类):

这被称为最优雅的懒加载

利用了jvm的机制,在加载外部类时,内部类不会被加载,只有在调用方法时,

才开始加载静态内部类,这个过程是线程安全的,同时效率也提升了。

public class InnerClassSingleton {
    
    private InnerClassSingleton() {}
    
    // 静态内部类
    private static class SingletonHolder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    
    public static InnerClassSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

枚举的方式:

核心概念

        1.用枚举实现单例模式,可以说是 《Effective Java》 这本书作者 Joshua Bloch 提倡的、

        2.目前实现单例的最佳实践。它的核心思想是:利用Java枚举类型本身的特性,让JVM来为

           我们保证单例的绝对唯一性,并防御各种攻击。

代码实现及使用(及其简单)

public class SingletonTest3 {
    public static void main(String[] args) {
        Singleton1 instance1 = Singleton1.INSTANCE;
        instance1.say();
    }
}
enum Singleton1 {
    INSTANCE;
    public void say() {
        System.out.println("ok");
    }
}

java中使用单例模式的源码例子:

在Runtime类中可以看出,它使用了饿汉式:


网站公告

今日签到

点亮在社区的每一天
去签到