单例模式

如有错误或有补充,以及任何的改进意见,请在评论区留下您的高见,同时文中给出大部分的示例

如果觉得本文写的不错,不妨点个赞,收藏一下,助力博主产生质量更高的作品

概念

单例模式(Singleton Pattern)是软件设计模式的一种,用于确保一个类只有一个实例,并提供一个全局访问点。这种模式通常用于需要频繁创建和销毁同一对象的场景,以减少系统资源的消耗和提高性能。

优缺点

优点:

实例控制:单例模式确保类只有一个实例,可以防止其他对象实例化自己的副本,从而确保所有对象都访问唯一实例。

节约资源:由于系统中只存在一个对象,可以节约系统资源,特别是在需要频繁创建和销毁对象的场景中,可以提高系统的性能。

避免对共享资源的多重占用:单例模式可以避免对共享资源的多重占用,有助于资源的集中管理和控制。

缺点:

扩展困难:由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。如果需要修改单例类的内部逻辑,则必须修改所有使用该单例的地方,不利于代码的模块化和可维护性。

职责过重:单例类通常承载了过多的职责,在一定程度上违背了"单一职责原则"。这会导致代码的复杂性增加,可读性和可维护性降低。

滥用风险:如果滥用单例模式,将一些本应该短生命周期的对象定义为单例,可能会导致内存泄漏和其他问题。例如,将数据库连接池定义为单例,可能会导致连接池溢出或资源耗尽。

状态丢失:在某些情况下,如果实例化的对象长时间不被利用,系统可能会将其视为垃圾回收,导致对象状态的丢失。

使用场景

比如Spring的IOC容器非懒加载的Bean,网页浏览计数等。

实现方式

饿汉式

饿汉式是在类加载时即实例化单例对象 的一种方式。在类加载时,饿汉式单例实例就会被创建。这种方式可以确保在程序启动时就能使用单例对象,但是如果在整个应用程序中并不需要使用该单例对象,则会造成资源浪费

示例

SingletonClass1.java

// 饿汉式
public class SingletonClass1 {
    // 创建唯一对象
    private static SingletonClass1 instance = new SingletonClass1();

    // 避免实例化
    private SingletonClass1() {
    }

    // 获取唯一对象
    public static SingletonClass1 getInstance() {
        return instance;
    }

    public void hello () {
        System.out.println("This is SingletonClass1");
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        SingletonClass1.getInstance().hello();
    }
}

懒汉式

懒汉式是在需要使用单例对象时才进行实例化的一种方式。在类加载时,懒汉式单例实例并不会被创建,而是在第一次调用时才创建。这种方式可以避免不必要的资源浪费 ,但在多线程环境下可能会出现问题。在懒汉式实现中,需要将单例的实例化放在一个公共的静态方法中,以确保线程安全

// 懒汉式
public class SingletonClass2 {
    // 初始不加载
    private static SingletonClass2 instance = null;

    // 避免实例化
    private SingletonClass2() {
    }

    // 获取对象的方法
    public static synchronized SingletonClass2 getInstance() {
        if (instance == null) {
            instance = new SingletonClass2();
        }
        return instance;
    }

    public void hello() {
        System.out.println("This is SingletonClass2");
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        SingletonClass2.getInstance().hello();
    }
}

以上的懒汉式实现效率较低,每次都要通过synchronized,效率较低

以下是通过双重锁检查机制来使其更加高效的例子:

双重检查锁定(Double-Checked Locking)是一种在多线程环境下使用的同步机制,主要用于实现单例模式。其基本原理是在第一次检查时验证实例是否已经创建,如果已经创建则直接返回实例,不需要进行后续的同步操作。如果实例尚未创建,则进入同步块,在同步块内部进行第二次检查,以确保只有一个线程创建实例。双重检查锁定的目的是在尽量减少同步操作的情况下,保证在多线程环境中只有一个实例被创建,这样可以避免性能下降,同时保证线程安全。需要注意的是,在使用双重检查锁定时,需要使用volatile关键字修饰实例变量,以确保其可见性,避免指令重排序导致的问题。此外,由于不同版本的JVM和编译器对指令重排序的处理方式可能不同,因此在使用双重检查锁定时,需要仔细考虑各种因素,确保其正确性和可靠性。

双重锁检查机制效率更高的原因主要有以下几点:

减少同步锁的调用次数:在懒汉式单例模式中,如果不使用双重锁检查机制,每次调用 getInstance() 方法时都需要获取同步锁,这会导致性能下降。而双重锁检查机制通过第一次检查减少了获取同步锁的次数,只有在实例未创建的情况下才会进入同步块,从而提高了性能。

提升并发度,降低开销:由于双重锁检查机制在第一次检查时只进行了简单的 null 检查,而这个操作是非常快速的,开销也比较小,因此它可以快速地释放同步锁,让其他线程有机会进入同步块,从而提高了并发度。同时也可以降低开销,提高性能。

SingletonClass2Plus.java

// 懒汉式Plus
public class SingletonClass2Plus {
    // 初始不加载
    private static volatile SingletonClass2Plus instance = null;

    // 避免实例化
    private SingletonClass2Plus() {
    }

    public static SingletonClass2Plus getInstance() {
        // 第一次检查
        if (instance == null) {
            synchronized (SingletonClass2Plus.class) {
                // 第二次检查
                if (instance == null) {
                    instance = new SingletonClass2Plus();
                }
            }
        }
        return instance;
    }

    public void hello() {
        System.out.println("This is SingletonClass2Plus");
    }
}

Main.java

public class Main {
    public static void main(String[] args) {
        SingletonClass2Plus.getInstance().hello();
    }
}
相关推荐
耀耀_很无聊2 小时前
第1章 初识SpringMVC
java·spring·mvc
麻衣带我去上学2 小时前
Spring源码学习(一):Spring初始化入口
java·学习·spring
东阳马生架构2 小时前
MySQL底层概述—1.InnoDB内存结构
java·数据库·mysql
手握风云-3 小时前
数据结构(Java版)第一期:时间复杂度和空间复杂度
java·数据结构
坊钰3 小时前
【Java 数据结构】时间和空间复杂度
java·开发语言·数据结构·学习·算法
飞升不如收破烂~3 小时前
Redis的String类型和Java中的String类在底层数据结构上有一些异同点
java·数据结构·redis
苹果酱05673 小时前
windows安装redis, 修改自启动的redis服务的密码
java·开发语言·spring boot·mysql·中间件
feilieren4 小时前
信创改造 - TongRDS 替换 Redis
java·spring boot·后端
Allen Bright4 小时前
Jedis连接池的操作
java·redis
庞传奇4 小时前
【LC】560. 和为 K 的子数组
java·算法·leetcode