当前位置: 首页 > article >正文

单例模式:确保一个类只有一个实例

目录

引言

1. 单例模式的核心思想

2. 单例模式的实现方式

2.1 饿汉式单例

2.2 懒汉式单例

2.3 线程安全的懒汉式单例

2.4 双重检查锁定(Double-Checked Locking)

2.5 静态内部类实现单例

2.6 枚举实现单例

3. 单例模式的使用场景

4. 单例模式的优缺点

优点:

缺点:

5. 总结


引言

单例模式(Singleton Pattern)是设计模式中最简单且最常用的创建型模式之一。它的核心思想是确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式在许多场景中非常有用,例如配置管理、线程池、数据库连接池等。


1. 单例模式的核心思想

单例模式的核心思想是:

  1. 私有化构造函数:防止外部通过 new 关键字创建实例。

  2. 提供一个静态方法:用于获取类的唯一实例。

  3. 确保唯一性:在整个应用程序生命周期中,类的实例只有一个。


2. 单例模式的实现方式

单例模式有多种实现方式,每种方式都有其优缺点。以下是几种常见的实现方式:

2.1 饿汉式单例

饿汉式单例在类加载时就创建实例,因此是线程安全的。

public class Singleton {
    // 在类加载时创建实例
    private static final Singleton INSTANCE = new Singleton();

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

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

饿汉式是最简单的单例模式的写法,保证了线程的安全,在很长的时间里,我都是饿汉模式来完成单例 的,因为够简单,后来才知道饿汉式会有一点小问题,看下面的代码:

 public class Hungry {
    private byte[] data1 = new byte[1024];
    private byte[] data2 = new byte[1024];
    private byte[] data3 = new byte[1024];
    private byte[] data4 = new byte[1024];
    
    private Hungry() {
    }
    private final static Hungry hungry = new Hungry();
    public static Hungry getInstance() {
        return hungry;
    }
 }

 在Hungry类中,我定义了四个byte数组,当代码一运行,这四个数组就被初始化,并且放入内存了,如 果长时间没有用到getInstance方法,不需要Hungry类的对象,这不是一种浪费吗?我希望的是 只有用 到了 getInstance方法,才会去初始化单例类,才会加载单例类中的数据。所以就有了 第二种单例模 式:懒汉式。

优点

  • 实现简单,线程安全。

缺点

  • 如果实例未被使用,会造成资源浪费。


2.2 懒汉式单例

懒汉式单例在第一次调用 getInstance() 时才创建实例。

public class LazyMan {
    private LazyMan() {
        System.out.println(Thread.currentThread().getName()+"Start");
    }
    private static LazyMan lazyMan;
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
    // 测试并发环境,发现单例失效
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
                  }).start();
        }
    }
 }

缺点

  • 线程不安全,多个线程可能同时进入 if (instance == null) 条件,导致创建多个实例。


2.3 线程安全的懒汉式单例

通过在 getInstance() 方法上加锁,可以解决懒汉式单例的线程安全问题。

public class LazyMan {
    private LazyMan() {
    }
    private static LazyMan lazyMan;
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
 }

保证了线程的安全性,又符合了懒加载,只有在用到的时候,才会去初始化,调用 效率也比较高,但是这种写法在极端情况还是可能会有一定的问题。因为 :

lazyMan = new LazyMan();

不是原子性操作,至少会经过三个步骤:

1. 分配对象内存空间

2. 执行构造方法初始化对象

3. 设置instance指向刚分配的内存地址,此时instance !=null;

由于指令重排,导致A线程执行 lazyMan = new LazyMan();的时候,可能先执行了第三步(还没执行第 二步),此时线程B又进来了,发现lazyMan已经不为空了,直接返回了lazyMan,并且后面使用了返回 的lazyMan,由于线程A还没有执行第二步,导致此时lazyMan还不完整,可能会有一些意想不到的错 误,所以就有了下面一种单例模式。


2.4 双重检查锁定(Double-Checked Locking)

双重检查锁定是一种优化后的线程安全懒汉式单例实现方式。

这种单例模式只是在上面DCL单例模式增加一个volatile关键字来避免指令重排:

 public class LazyMan {
    private LazyMan() {
    }
    private volatile static LazyMan lazyMan;
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}

优点

  • 线程安全,且只有在第一次创建实例时加锁,性能较好。

注意

  • 必须使用 volatile 关键字,防止指令重排序导致的问题。


2.5 静态内部类实现单例

静态内部类实现单例是一种优雅且线程安全的方式。

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优点

  • 线程安全,且只有在调用 getInstance() 时才会加载 SingletonHolder 类,实现懒加载。


2.6 枚举实现单例

枚举实现单例是《Effective Java》推荐的方式,它天然支持线程安全和防止反射攻击。

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("Doing something...");
    }
}

优点

  • 线程安全,代码简洁,防止反射和序列化破坏单例。


3. 单例模式的使用场景

单例模式适用于以下场景:

  1. 全局配置管理:例如读取配置文件,确保配置信息全局唯一。

  2. 数据库连接池:确保连接池只有一个实例,避免资源浪费。

  3. 日志管理:确保日志记录器全局唯一。

  4. 线程池:确保线程池的唯一性,避免重复创建线程。


4. 单例模式的优缺点

优点:

  • 节省资源:避免重复创建对象,减少内存开销。

  • 全局访问点:方便对唯一实例的管理和访问。

缺点:

  • 扩展性差:单例类通常难以扩展,因为其构造函数是私有的。

  • 违背单一职责原则:单例类既负责创建实例,又负责业务逻辑。

  • 测试困难:单例类的全局状态可能导致测试困难。


5. 总结

单例模式是一种简单但强大的设计模式,适用于需要全局唯一实例的场景。通过不同的实现方式(如饿汉式、懒汉式、双重检查锁定、静态内部类、枚举等),可以满足不同的需求。

在实际开发中,应根据具体场景选择合适的单例实现方式。如果需要懒加载且线程安全,推荐使用静态内部类或枚举实现;如果需要更高的性能,可以考虑双重检查锁定。


http://www.kler.cn/a/577829.html

相关文章:

  • C# 初学者的系统学习路径与职业规划指南
  • Prompt Engineering的技术发展趋势
  • 在Spring Boot项目中分层架构
  • vue3如何配置环境和打包
  • 【Python 数据结构 10.二叉树】
  • 十、Redis 主从复制:原理解析、配置实践与优化策略
  • Linux上位机开发(开篇)
  • 创新科技,绿色防护——稀土抑烟剂让板材更安全
  • API安全防护探析:F5助企业应对关键安全挑战
  • 解决Jenkins默认终止Shell产生服务进程的问题
  • React基础之 forwardRef
  • 【Java代码审计 | 第四篇】SQL注入防范
  • uniapp实现微信小程序一键登录
  • 神经网络|(十四)|霍普菲尔德神经网络-Hebbian训练
  • Django模型数据查询:深入探索模型管理器Model.objects
  • MySQL表约束
  • 【js逆向】iwencai国内某金融网站实战
  • PDF 文件中的文本链接是如何定义的?
  • 在 CLion 中使用 Boost.Test 进行 C++ 单元测试
  • CentOS 7 aarch64上制作kernel rpm二进制包 —— 筑梦之路