设计模式之【单例模式】详解

设计模式一单例模式

一、概述

单例模式(Singleton Pattern)是一种常用的软件设计模式,其核心思想是确保一个类仅有一个实例,并提供一个全局访问点来获取这个实例。单例模式主要用于控制资源的访问,比如配置文件的读取,数据库的连接等,通过确保这类资源全局只有一个实例,既可以避免对资源的多重占用,又可以减少系统的性能开销。

二、结构

私有构造方法: 防止外部通过new关键字直接创建对象实例。
静态成员变量: 类内部维护一个类型为自身的静态私有变量,用于存放唯一实例,确保全局唯一性。
公有静态方法: 外部通过调用这个静态方法来获取单例类的唯一实例。此方法需负责创建和管理唯一实例,通常命名为getInstance()。

三、实现

3.1饿汉式(预加载)

概述

  1. 类加载时就初始化好实例对象,容易产生垃圾对象,浪费内存。
  2. 无锁,执行效率高。
  3. 天生线程安全,因为类加载时就已初始化好实例。

类图
在这里插入图片描述
实现方式一:

定义静态变量实例:
**
 * 饿汉式:预加载
 * 静态成员变量
 */
public class SingleObject {
    //定义静态成员变量获取本类的实例
    private static final SingleObject INSTANCE = new SingleObject ();

    //私有构造方法,避免通过new关键字来实例化对象,保证只存在一个实例
    private SingleObject (){}

    //提供一个公共的访问类,让外界获取该对象
    public static SingleObject getINSTANCE() {
        return INSTANCE;
    }
}
静态代码块初始化实例:
/**
 * 饿汉式的第二种实现方式:
 * 使用静态代码块的方式
 */
public class SingleObject {

    //声明Singleton2类型的静态常量成员变量,但是没有初始化,在静态代码块中对其进行初始化
    private static final SingleObject INSTANCE;

    //在静态代码块中对成员常量初始化
    static {
        INSTANCE = new SingleObject ();
    }
    //私有化构造方法,避免外部使用new关键字创建实例
    private SingleObject (){}

    //定义公共方法,对外提供获取单例实例的接口
    public static SingleObject getINSTANCE() {
        return INSTANCE;
    }
}

分析

没有使用该单例对象,该对象就被加载到了内存,会造成内存的浪费。

枚举方式:

概述:

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/**
 * 枚举型单例
 * @author Andya
 * @date 2021/3/9
 */
public enum EnumSingleInstance {
    INSTANCE;

    public EnumSingleInstance getInstance(){
        return INSTANCE;
    }
}

3.2 懒汉式(懒加载)

概述:

  1. 饿汉式属于延迟加载初始化。
  2. 若不加锁,则为非线程安全的。
  3. 若加锁,则为线程安全的,但效率很低,基本上每次都要同步。
无锁实现
/**
 * 无锁懒汉式单例,非线程安全
 * @date 2021/3/9
 */
public class LanHanNoLockSingleInstance {

    //懒汉式:初始化时不创建实例,等需要时再创建
    private static LanHanNoLockSingleInstance instance;

    private LanHanNoLockSingleInstance() {
        doSomething();
    }

    public static LanHanNoLockSingleInstance getInstance() {
        //先判断是否为空,若为空创建一个新的实例
        if (instance == null) {
            instance = new LanHanNoLockSingleInstance();
        }

        return instance;
    }

    public void doSomething() {

    }
}

分析:

即使类第一次使用时候,也并没有进行对象的赋值操作,直到调用getInstance方法判断为null时候才会创建,实现了使用才创建的行为。避免了内存的浪费。但是,多线程下,会出现线程安全问题,会导致同时创建多个对象,违背一个类只有一个实例原则。

加synchronized 锁实现
/**
 * 加锁懒汉式单例,线程安全
 * @date 2021/3/9
 */
public class LanHanWithLockSingleInstance {

    //懒汉式:初始化时不创建实例,等需要时再创建
    private static LanHanWithLockSingleInstance instance;

    private LanHanWithLockSingleInstance() {
        doSomething();
    }

    public static synchronized LanHanWithLockSingleInstance getInstance() {
        //先判断是否为空,若为空创建一个新的实例
        if (instance == null) {
            instance = new LanHanWithLockSingleInstance();
        }

        return instance;
    }

    public void doSomething() {

    }
}

分析:

加锁实现了线程安全问题。但是同时导致该方法执行效率变低。从代码可以看出,当调用过一次getInstance()方法后,线程安全问题就不会在存在,此时若每次重新执行getInstance()方法都要进行重锁检查,导致效率特别低。

双重检锁式单例

在多线程应用中使用这种模式可以保证线程安全。因为如果实例为空,有可能存在两个线程同时调用getInstance()方法的情况,这样的话,第一个线程会首先使用新构造器实例化一个单例对象,但此时它还没有完成单例对象的实例化操作,同时第二个线程也检查到单例实例为空,也会开始实例化单例对象,这就造成了2次实例化对象。所以多线程应用中,需要锁来检查实例是否线程安全。

虽然加锁可以保证线程安全,但是会带来延迟,因为加锁后,代码块在同一时刻只能被一个线程执行,但是同步锁只有在实例没被创建的时候才会起作用。如果单例实例已经被创建,其实不需要走该步骤。因此,我们可以在代码块锁外面再加一层实例空判断。instance == null被检查2次。

if (instance == null) {
	synchronized(SingleInstance.class){
		if (instance == null) {
			instance = new SingleInstance();
		}
	}
}

实现

/**
 * 双检锁单例
 * @date 2021/3/9
 */
public class DoubleCheckSingleInstance {

    //使用volatile保证多线程的可见性
    private static volatile DoubleCheckSingleInstance instance;

    private DoubleCheckSingleInstance() {
        doSomething();
    }

    public static DoubleCheckSingleInstance getInstance() {
        //第一次检查是否创建过该单例
        if (instance == null) {
            //加锁,保证线程安全
            synchronized (DoubleCheckSingleInstance.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleInstance();
                }
            }
        }
        return instance;
    }

    public void doSomething() {

    }
}

分析:

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题。

静态内部类单例
  1. 延迟加载初始化实例instance。
  2. 线程安全。
  3. 该方式只适用于静态域的情况。
  4. 静态内部类单例模式中实例由内部类创建,由于JVM在加载外部内的过程中,是不会加载静态内部类的,只有内部类的属性或者方法被调用时,才会被加载,并初始化其静态属性。
/**
 * 静态内部类单例
 * @date 2021/3/9
 */
public class StaticInternalSingleInstance {

    //静态内部类
    private static class StaticInternalSingleInstanceHolder{

        private static final StaticInternalSingleInstance INSTANCE
                = new StaticInternalSingleInstance();

    }

    private StaticInternalSingleInstance() {
        doSomething();
    }

    public static final StaticInternalSingleInstance getInstance() {
        return StaticInternalSingleInstanceHolder.INSTANCE;
    }

    public void doSomething() {

    }

}

四、总结

一般在开发应用中,建议使用第1种饿汉式单例,而不推荐使用第2种懒汉式单例,除非明确需要延迟加载时,才会使用第4种静态内部类单例,若涉及到反序列化创建对象时,推荐使用枚举式单例。其他也可以考虑使用第3种双重检锁式单例。

本人水平有限,有错的地方还请批评指正。

什么是精神内耗?
简单地说,就是心理戏太多,自己消耗自己。
所谓:
言未出,结局已演千百遍;
身未动,心中已过万重山;
行未果,假想灾难愁不展;
事已闭,过往仍在脑中演。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的小熊猫_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值