问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

掌握单例模式,让你的代码更优雅!

创作时间:
作者:
@小白创作中心

掌握单例模式,让你的代码更优雅!

引用
CSDN
9
来源
1.
https://blog.csdn.net/2301_80293232/article/details/136573382
2.
https://blog.csdn.net/qq_62571013/article/details/140544999
3.
https://blog.csdn.net/qq_61563387/article/details/136258596
4.
https://blog.csdn.net/Shine0115/article/details/136441347
5.
https://blog.csdn.net/ljx1400052550/article/details/139509099
6.
https://developer.aliyun.com/article/1042370
7.
https://cloud.tencent.com/developer/article/2472404
8.
https://cloud.tencent.com/developer/article/2425327
9.
https://developer.aliyun.com/article/1205880

单例模式是Java中最简单的设计模式之一,属于创建型模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。通过饿汉式、懒汉式等实现方法,单例模式能够有效提升代码效率和资源利用率。掌握单例模式,不仅能让你的代码更加简洁高效,还能避免不必要的资源浪费,使你的编程之路如虎添翼。

01

单例模式的实现方式

单例模式主要有两种实现方式:饿汉式和懒汉式。

饿汉式单例模式

饿汉式单例模式是指在类加载时就创建了该类的实例,因此被称为“饿汉”模式。在这种模式下,无论是否需要使用该实例,都会提前创建出来。这样可以保证在后续的使用中,直接返回该实例,没有额外的线程安全问题。

//饿汉式构建单例类
class Singleton {
    //1.静态私有数据成员,产生唯一对象
    private static Singleton single = new Singleton();
    //2.私有化构造方法
    private Singleton() { }
    //3.取得唯一实例对象的公共静态成员方法
    public static Singleton getInstance() {
        return single;
    }
}

饿汉式的缺点是可能会浪费内存空间,因为实例在类加载时就被创建了。

懒汉式单例模式

懒汉式单例模式是指在需要使用该实例时才进行创建,因此被称为“懒汉”模式。在这种模式下,首次调用获取实例的方法时,才会进行实例化。

懒汉式的优点是节省了内存空间,只有在需要使用时才会创建实例。然而,懒汉式的实现需要考虑线程安全问题,需要在获取实例的方法中进行同步操作,以避免多线程下出现重复创建实例的情况。

//懒汉式构建单例类
class Singleton{
    //1.静态私有数据成员,没有产生唯—对象
    private static Singleton single= null; 
    private Singleton() { }         //2.私有化构造方法
    //3.取得唯一实例对象的公共静态成员方法,如果没有唯一实例化对象则产生该对象
    public static Singleton getInstance(){
        if (single==null)
            single= new Singleton();
        return single; 
    }
}

为了保证线程安全,可以使用双重校验锁(Double-Checked Locking)的方式:

public class SingletonLazy {
    //定义一个私有构造方法
    private SingletonLazy(){}
    //创建该类对象
    private volatile static SingletonLazy instance;
    //对外提供静态方法获取该对象 , 加双重锁
    public static SingletonLazy getInstance(){
        if(instance==null){
            synchronized (instance.getClass()){
                if(instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

此外,还可以使用静态内部类的方式,利用JVM类加载机制保证线程安全:

public class SingletonLazy {
    //私有构造方法
    private SingletonLazy() {}
    //定义静态类
    private static class SingletonHolder {
        //静态方法
        private static final SingletonLazy INSTANCE = new SingletonLazy();
    }
    //对外提供静态方法获取该对象
    public static SingletonLazy getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
02

单例模式的优势与局限性

单例模式具有以下优势:

  1. 资源优化:单例模式通过避免重复创建对象来减少内存消耗,特别是在频繁实例化会导致性能下降的场景下。
  2. 统一访问控制:它为唯一实例提供了受控的全局访问方式,并简化了对共享资源的管理。
  3. 防止资源多重占用:例如,在写文件操作中,单例模式可以避免多个线程同时写入同一文件的问题。
  4. 系统设置与资源共享:适用于需要集中管理和优化资源访问的场景,如数据表映射处理。

然而,单例模式也存在一些局限性:

  1. 扩展困难:缺乏抽象层导致单例类难以扩展,限制了灵活性。
  2. 职责过重:单例类可能承担过多责任,违背单一职责原则。
  3. 滥用风险:过度使用可能导致问题,比如数据库连接池溢出或对象状态丢失。
03

单例模式的应用场景

单例模式在实际开发中有着广泛的应用场景:

  1. 配置管理器:提供全局唯一的配置信息访问点。
  2. 日志记录器:集中管理日志输出,方便追踪程序运行状态。
  3. 数据库连接池:统一管理数据库连接,提高资源利用率。
  4. 缓存管理器:高效管理缓存数据,提升访问速度。
04

单例模式 vs 原型模式

在Spring框架中,单例模式是默认的bean作用域。在IOC容器中,每个ID只对应一个bean实例,该实例在第一次创建后会被缓存,以后每次请求该bean,都会直接返回第一次创建的实例。这样可以确保在一个Spring IoC容器的上下文中,每个bean都是唯一的。

而原型模式在每次请求它的时候都会创建一个新的bean实例,即使同一个ID,由于作用域是原型,所以返回的也是不同的实例。

@Configuration
public class AppConfig {
    @Bean
    @Scope("prototype")
    public StudentService studentService() {
        return new StudentService();
    }
}

在单例模式下(即没有取消注释 @Scope("prototype") 行), bean1bean2 实际上指向同一个对象,它们的哈希码也因此相同。在原型模式下(即取消注释 @Scope("prototype") 行),每次通过 context.getBean(MyBean.class); 获取的都将是不同的对象,因此, bean1bean2 的哈希码将会不同,表明它们是不同的对象。

原型模式的应用场景可以大致的参考一下以下案例:

  • 状态不可共享bean:假设我们有一个 ShoppingCart 类,这个类包含了当前用户的购物车信息:用户ID,商品列表等。由于每个用户的购物车都是不同的,这就需要为每个用户创建一个新的购物车实例。这样每一个实例就可以拥有自己的状态(用户ID,商品列表),并且这些状态之间不会互相干扰。在这种情况下,为 ShoppingCart 设置成原型模式就很合适
  • 重要资源对象:对于一些创建和销毁成本较高的对象,如果每次需要时都去创建和销毁,那么系统的开销会非常大。例如,在一些大型软件应用中,可能会为了提供服务而创建许多对数据库的连接(数据库连接的建立和销毁成本高),这个时候可以将数据库连接设置为原型模式,这样每次需要数据库连接时,都从Spring容器获取一个新的实例,替代新建一个数据库连接。
  • 在运行时动态确定,创建哪一类的实例:在有些情况下,你可能需要在运行时动态决定创建哪类对象。例如,你有多种排序算法类,这些算法类都实现自同一个接口。你可以在运行时,基于不同的输入或者条件,选择使用哪种排序算法,这时,可以将每种排序算法类设为原型Bean,这样就可以动态地从Spring中获取特定的排序算法实例。

在实际开发中,选择哪种模式取决于具体需求。如果Bean是有状态的,或者需要每次都得到一个全新的对象实例,那么原型模式可能是一个好选择。如果Bean是无状态的,或者多个组件共享一个Bean实例没有问题,那么单例模式可能更有优势,因为它可以减少对象创建的开销。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号