Java 中实现单例模式

单例模式

单例模式,就是一个类在任何情况下绝对只有一个实例,并且提供一个全局访问点来获取该实例。
要实现单例,至少需要满足两个点:

  • 私有化构造方法,防止被外部实例化造成多实例问题
  • 提供一个静态方位作为全局访问点来获取唯一的实例对象

在 Java 里面,至少有 6 种方法来实现单例。

实现

第一种

第一种, 是最简单的实现,通过延迟加载的方式进行实例化,并且增加了同步 锁机制避免多线程环境下 的线程安全问题.

java 复制代码
public class Singleton {
    private static  Singleton instance;
    private Singleton(){}
    public static  synchronized Singleton getInstance(){
        if (instance == null){
            instance=new Singleton();
        }
        return instance;
    }
}

但是这种加锁会造成性能问题,而且同步锁只有在第一次实例化的时候才产生作用,后续不需要。

第二种

第二种,通过双重检查锁的方式,减少了锁的范围来提升性能

java 复制代码
public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {
        // 私有构造函数
    }

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

instance 使用 volatile 关键字修饰,以确保多线程环境下的可见性和有序性。

使用volatile关键字修饰instance变量,主要是为了保证在多线程环境下获取单例实例的可见性和有序性。具体来说:

  1. 可见性:当一个线程第一次访问getInstance()方法时,如果instancenull,那么该线程将进入同步块并创建实例。这个写操作对于其他线程来说是可见的,即它们将立即看到instance的新值。这就避免了在一个线程创建实例后,其他线程仍然看到instancenull的情况。

  2. 有序性:在双重检查锁中,由于编译器和处理器的优化行为,可能会发生指令重排序。如果没有使用volatile关键字修饰instance,那么在某些情况下,其他线程可能会看到指令重排后的顺序,从而导致单例实例的未完全初始化。而使用volatile修饰instance后,禁止了这种指令重排序优化,保证了实例的完整性。

第三种

第三种,通过饿汉式实现单例。这种方式在类加载的时候就触发了实例化,从而避免了多线程同步问题。

java 复制代码
public class Singleton {
    private static Singleton instance=new Singleton();
    private Singleton(){}
    public static  Singleton getInstance(){
        return instance;
    }
    
}

第四种

第四种,通过在静态块里面实例化,而静态块是在类加载的时候触发执行的,所以也只会执行一
次。

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    static {
        instance=new Singleton();
    }
    private Singleton(){}
    public Singleton getInstance(){
        return instance;
    }

}

上面两种方式,都是在类加载的时候初始化,没有达到延迟加载的效果,当然本身影响不大。

第五种

由于静态内部类只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类。

java 复制代码
public class Singleton {
    private static class SingletonHolder{
        private static final Singleton INSTANCE=new Singleton();
    }
    private Singleton(){}
    public static final Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

所以当 Singleton 被加载的时候不会初始化 INSTANCE,从而实现了延迟加载。

第六种

我们还可以使用枚举类来实现。

java 复制代码
public enum Singleton {
    INSTANCE;

    // 添加其他成员变量和方法

    public void doSomething() {
        // 单例实例的操作
    }
}

这种写法既能避免多线程同步问题,又能防止反序列化重新创建新对象,也是一个比较好的方案。

总结

我认为大体分为 3 种方式来实现单例:

  • 第一种是通过双重检查锁的方式,它是一种线程安全并且是延迟实例化的方式,但是因为加锁,所以会有性能上的影响。

  • 第二种是通过静态内部类的方式实现,它也是一种延迟实例化,由于它是静态内部类,所以只会使用的时候加载一次,不存在线程安全问题。

  • 第三种是通过枚举类的方式实现,它既是线程安全的,又能防止反序列化导致破坏单例问题
    多线程、克隆、反序列化、反射,都有可能会造成单例的破坏。而我认为,通过枚举的方式实现单例,是能够解决所有可能被破坏的情况。

相关推荐
Cheng小攸16 小时前
入侵检测环境部署
开发语言·php
Smoothcloud润云16 小时前
5大功能精修,重构AI算力使用体验!
java·人工智能·windows·算法·重构·编辑器·sublime text
我是唐青枫16 小时前
Java MyBatis-Flex 实战指南:从 BaseMapper 到 QueryWrapper 的轻量 ORM 用法
java·开发语言·mybatis
顺风尿一寸16 小时前
Java Native 方法底层原理深度解析:从 JNI 注册到 Native Wrapper 生成
java
极客先躯17 小时前
高级java每日一道面试题-2026年01月18日-实战篇[Docker]-如何清理仓库中的旧镜像?
java·运维·docker·容器
ShyanZh17 小时前
Markitdown 多格式文档智能解析实战指南
开发语言·c#
一只专注api接口开发的技术猿17 小时前
OpenClaw 对接淘宝商品 API,低成本实现全天候选品监控|附可运行 Python 实操代码
大数据·开发语言·数据库·python
xingpanvip17 小时前
星盘接口开发文档:马盘次限盘接口指南
android·开发语言·python·php·lua
FBI HackerHarry浩17 小时前
第二阶段Day07【Python生成器、yield关键字、property、正则表达式】
开发语言·python·正则表达式
iiiiyu17 小时前
IO流(二)
java·开发语言·数据结构·编程语言