Java 设计模式之单例模式

Java 设计模式之单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一种访问该实例的全局方法。这种模式有助于确保系统中的某些组件只有一个实例,并提供了一种方便的方法来访问该实例。

更多设计模式请参考:Java 中的 23 种设计模式详解

使用场景
  • 需要频繁进行创建和销毁的对象;
  • 创建对象时耗时过多或耗费资源过多,但又经常使用的对象;
  • 工具类对象;
  • 频繁访问数据库或文件的对象。

在Java中,单例模式通常使用一个私有构造函数和一个静态getInstance方法来实现。以下是Java中单例模式的几种示例:

1.1 饿汉式(静态常量)【可用】

优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

java 复制代码
public class Singleton {
    // 在类加载时就创建实例
    private static final Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}

1.2 饿汉式(静态代码块)【可用】

这种方式和上面的方式类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候执行静态代码块中的代码,初始化类的实例。优缺点和上面一样。

java 复制代码
public class Singleton {
    // 在类加载时创建实例
    private static final Singleton instance;

    // 静态代码块,在类加载时执行
    static {
        instance = new Singleton();
    }

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}

1.3 懒汉式(线程不安全)【不可用】

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

java 复制代码
public class Singleton {
    // 类加载时不创建实例
    private static Singleton instance;

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

1.4 懒汉式(线程安全,同步方法)【不推荐用】

解决上面线程不安全问题的方式是对getInstance()方法进行同步。但这种方式效率太低,每个线程在想获得类的实例时,执行getInstance()方法都要进行同步。其实这个方法只需执行一次实例化代码,后面直接return实例化对象即可。

java 复制代码
public class Singleton {
    // 类加载时不创建实例
    private static Singleton instance;

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点,使用同步方法保证线程安全
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

1.5 懒汉式(线程安全,同步代码块)【不可用】

这种方式尝试使用同步代码块来提高效率,但仍无法保证线程安全。如果一个线程进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

java 复制代码
public class Singleton {
    // 类加载时不创建实例
    private static Singleton instance;

    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 提供全局访问点,尝试使用同步代码块
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

1.6 双重检查【推荐使用】

Double-Check概念对于多线程开发者来说不会陌生,进行了两次if (instance == null)检查,这样就可以保证线程安全。实例化代码只需执行一次,后面再次访问时,判断if (instance == null),直接return实例化对象。

优点:线程安全;延迟加载;效率较高。

java 复制代码
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;
    }
}

1.7 静态内部类【推荐使用】

这种方式与饿汉式方式类似,都是采用类装载机制来保证实例化时只有一个线程。但不同的是,饿汉式在类装载时就实例化,没有Lazy-Loading效果,而静态内部类方式在需要实例化时才装载静态内部类,从而实例化Singleton。类的静态属性只会在第一次加载类时初始化,所以这里JVM帮助保证了线程的安全性。

优点:避免了线程不安全,延迟加载,效率高。

java 复制代码
public class Singleton {
    // 私有构造函数,防止外部实例化
    private Singleton() {}

    // 静态内部类,负责持有单例实例
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 提供全局访问点,通过静态内部类实现延迟加载和线程安全
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

1.8 枚举【推荐使用】

创建枚举默认就是线程安全的,不需要担心double checked locking,并且还能防止反序列化导致重新创建新的对象。枚举让JVM保证线程安全和单一实例问题,是JDK1.5版本后最适合用于创建单例设计模式的方法,是唯一一种不会被反射破坏单例状态的模式。

java 复制代码
// 使用枚举实现单例模式
public enum Singleton {
    // 定义一个枚举元素,即为单例实例
    INSTANCE;

    // 可以定义其他方法
    public void someMethod() {
        // 实现方法逻辑
    }
}

// 测试枚举单例
public class TestSingleton {
    public static void main(String[] args) {
        // 获取单例实例
        Singleton singleton = Singleton.INSTANCE;

        // 调用单例的方法
        singleton.someMethod();
    }
}

总结

推荐使用双重检查锁定、静态内部类和枚举方式,它们都能保证线程安全、高效并且实现延迟加载。这些方法不仅解决了线程安全问题,还能在需要时才进行实例化,节约了系统资源。选择哪种实现方式,取决于具体的需求和使用场景。

相关推荐
金銀銅鐵30 分钟前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
福建佰胜张工1 小时前
SMC 薄型气爪 MHF2-16D1 深度解析:平行开闭、低剖面结构在自动化装配中的选型与落地
java·服务器·安全·自动化
catoop1 小时前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
system_error_x2 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
驭渊的小故事2 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客2 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn1883 小时前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn3 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su4 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
我命由我123455 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法