如何优雅的理解单例模式

什么单例模式 ?

单例模式,也称单子模式,属于一种创建型模式。在这种模式下,保证一个类只能有一个实例对象,而且该类负责创建唯一的实例对象。

设计原则:构造方法私有化、静态方法返回对象、对象只进行一次实例化

例如有一个 SingleObject 类,通过 SingleObject.getInstance() 返回的所有对象的引用,都是完全相同的。

public class SingleObject {
​
    // 构造函数私有化
    private SingleObject() {}
    
    // 对象只进行一次实例化
    private static final SingleObject instance = new SingleObject();
    
    // 静态方法返回对象
    public static SingleObject getInstance(){
        return instance;
    }
}
 

为什么要使用单例模式 ?

一、节约资源

单例模式,保证内存中只有一个实例对象,一方面节省内存空间;另一方面避免进行重复的对象创建和销毁造成资源的浪费。

例如,以前的打井取水就是一种单例模式的实现,我们只需要打一次井,就可以一直取水。而不是每次重新打井,取水结束就把井给堵回去。其实,井 相当于一个单例对象,打井 就是一次实例化的过程,每次 取水 就是调用这个单例对象的方法。

日常开发中,单例模式的应用场景有很多,例如:

  • 数据库连接很耗资源,每次请求数据库都重新建立一次连接,显然很浪费资源。运用单例模式,实现一个数据库连接池,确保数据库驱动的连接类只被初始化一次,可以多次使用。

  • 默认情况下,Spring 容器中 Bean 的作用域是单例的,减少 Bean 实例实例化的消耗和 JVM 垃圾回收,以及可以快速获取 Bean 的实例对象。

  • 线程池的实现。

二、方便管理

例如,一些工具类,只需要一个实例来实现功能:

  • 生成唯一序列号

  • 网站的计数器,基于 Web 应用下,保证数值的同

三、避免多重占用

单例模式可以避免对资源的多重占用,例如 应用程序的日志应用,保证只有一个实例操作日志文件,避免日志文件被多个实例打开造成异常。

四、单例模式的缺点

1、扩展性差,想要进行扩展,通常只能修改代码。

2、单例模式与单一职责原则有冲突

怎么实现单例模式 ?

单例模式的实现方式有很多种,从广义的角度分为 饿汉式 和 懒汉式 。

  • 饿汉式分为:普通饿汉式、枚举方式。

  • 懒汉式分为:普通懒汉式、静态内部类、双重检验锁。

接下来,分别讲述这五种单例模式的具体实现方式。

普通饿汉式

public class SingleObject {
    // 构造函数私有化
    private SingleObject() {}
    
    // 创建实例对象
    // final:防止对象被修改,保证线程安全
    private static final SingleObject instance = new SingleObject();
    
    // 静态工厂方法,返回单例的实例对象
    public static SingleObject getInstance() {
        return instance;
    }
}

优点:

1、单例对象的创建是线程安全的,类加载的时候进行就会对单例对象初始化
2、首次获取单例对象的时候不需要加锁

缺点:

1、单例对象的创建不是延迟加载
2、反射可以破坏单例模式的约束,重复构建对象

枚举方式

public enum SingleObject {
    INSTANCE;
}

优点:

1、线程安全,类加载的时候进行就会对单例对象初始化
2、可以防止反射获取枚举类的私有构造方法
3、可以防止序列化破环单例模式

缺点:

1、单例对象的创建不是延迟加载
 

普通懒汉式

public class SingleObject {
    // 构造函数私有化
    private SingleObject() {}
    
    // 创建实例对象
    private static SingleObject instance = null;
    
    // 静态工厂方法,返回单例的实例对象
    public static SingleObject getInstance() {
        if(instance == null) {
            instance = new SingleObject();
        }
        return instance;
    }
}

优点:

1、延迟加载

缺点:

1、线程不安全,会创建出多个实例
2、反射可以破坏单例模式的约束,重复构建对象
 

静态内部类

public class SingleObject {
    // 构造函数私有化
    private SingleObject() {}
    
    private static SingleInner {
        private static final SingleObject instance = new SingleObject();
    }
​
    public static SingleObject getInstance() {
        return SingleInner.instance;
    }
}

优点:

1、延迟加载
2、线程安全,只有第一次调用 getInstance() 方法的时候,虚拟机才会加载 SingletonHolder 类,初始化单例对象 instance
3、单例对象的初始化不需要加锁。

缺点:

1、传参问题,单例对象是通过静态内部类进行初始化,外部无法传递参数进去
2、反射可以破坏单例模式的约束,重复构建对象
 

双重校验锁

public class SingleObject {
    // 构造函数私有化
    private SingleObject() {}
    
    // 创建实例对象
    // volatile:防止变量访问前后的指令重排,保证指令的执行顺序
    private volatile static SingleObject instance = null;
    
    // 静态工厂方法,返回单例的实例对象
    public static SingleObject getInstance() {
        // 第一重校验
        if(instance == null) {
            // 同步锁,注意这里是类级别的锁
            synchronized (SingleObject.class) {
                // 第二重校验
                if(instance == null) {
                    instance = new SingleObject();
                }
            }
        }
        return instance;
    }
}

优点:

1、延迟加载
2、线程安全,单例对象的初始化
3、单例对象的获取不需要加锁。

缺点:

1、反射可以破坏单例模式的约束,重复构建对象

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值