寂然解读设计模式 - 单例模式(上)

简介: 接下来我们来聊聊单例模式的八种实现方式,如下所示> 饿汉式(静态常量) > 饿汉式(静态代码块) > 懒汉式(线程不安全) > 懒汉式(线程安全,同步方法) > 懒汉式(线程安全,同步代码块) > 双重检查 > 静态内部类 > 枚举方式
I walk very slowly, but I never walk backwards 

设计模式 - 单例模式(上)


寂然

大家好~,我是寂然,本节课呢,我们正式进入设计模式的学习,第一个要聊的就是我们最常见的,同时也是面试高频的单例模式,那老规矩,我们直接启程吧

五重境界

在此之前,我们闲聊两句,因为有的小伙伴问到一个问题,怎么才算是学会或者说掌握设计模式了呢?那我们一起来聊聊这个话题,会有很多人说其实这个不好去定义,但是如果自己平时写代码,无意识,习惯成自然的用到了设计模式,遇到实际问题,很快就能对比选择,使用合适的设计模式,低耦合,合理的解决掉实际问题,我觉得就是很好的掌握了,网上有人分享了设计模式的五重境界,感觉很贴切,这里也拿来和大家分享下


第 1 层:刚开始学编程不久,听说过什么是设计模式 ,但不了解

第 2 层:有很长时间的编程经验了,自己写了很多代码,其中用到了设计模式,但是自己却不知道

第 3 层:学习过了设计模式,发现自己已经在使用了,并且发现了一些新的模式挺好用的

第 4 层:阅读了很多别人写的源码和框架,其中看到了设计模式,能够领会设计模式的精妙和带来的好处

第 5 层:代码写着写着,自己都没有意识到使用了设计模式,并且熟练的写了出来

问题答疑

同时,在开始之间还要和大家强调一句话,因为很多小伙伴都有说或者脑海中有这样一个疑问:这样就可以啊,为什么非要这么复杂呢,大家要注意,设计模式不仅仅是站在实现功能的角度上,而是立足于整个软件系统的设计,所以大家要学会在学习的过程中,总结和对比思考,可以达到事半功倍的效果

官方定义

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例并且该类只提供一个取得其对象实例的方法(静态方法)


举个最常见的例子,Spring 中的 bean 默认都是单例模式,每个bean定义只生成一个对象实例,每次getBean请求获得的都是此实例

单例模式八种方式

那接下来我们来聊聊单例模式的八种实现方式,如下所示

饿汉式(静态常量)

饿汉式(静态代码块)

懒汉式(线程不安全)

懒汉式(线程安全,同步方法)

懒汉式(线程安全,同步代码块)

双重检查

静态内部类

枚举方式

饿汉式(静态常量)

饿汉式之静态常量的写法如下:

一,构造器私有化(防止外部通过new + 构造器的方式创建对象实例)

二,类的内部创建对象(创建final,static的实例对象)

三,对外暴露一个公共的静态方法(通过该方法,返回该类唯一的对象实例)

// 饿汉式(静态变量)
class Singleton{
    //一 构造器私有化
    private Singleton(){

    }
    //类的内部创建对象
    private final static Singleton singleton = new Singleton();

    //对外提供公共的,静态的方法
    public static Singleton getInstance(){

            return singleton;
    }
}

public class SingletonDemo {
    public static void main(String[] args) {

        //测试
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
        //结果为 true,证明是同一个实例对象
    }
}
写法分析

优势

这种写法比较简单,就是在类装载的时候就完成了实例化,避免了线程同步问题

这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大 多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果

劣势

在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果,如果从始至终从未使用过这个实例,则会造成内存的浪费

饿汉式(静态代码块)

饿汉式(静态代码块)的方式,是在静态代码块中创建单例对象,示例代码如下图所示

//饿汉式 静态代码块
class Singleton{

    private Singleton(){

    }

    private final static Singleton singleton;

    static {
        singleton = new Singleton();
    }

    public static Singleton getInstance(){

        return singleton;
    }
}

public class Singletondemo02 {

    public static void main(String[] args) {

        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);

    }
}
写法分析

这种方式和上面的其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例,优劣势和第一种写法一致,写法可用,但可能会造成内存的浪费

饿汉式是不存在线程安全问题的

懒汉式(线程不安全)

懒汉式写法与饿汉式写法的不同在于,并不是一上来就要创建对象,而是当调用对应的getInstance()方法时,才会进行创建,示例代码如下图所示

// 懒汉式(线程不安全)
class Singleton{

    //构造器私有化
    private Singleton(){ 

    }

    //类的内部创建对象
    private static Singleton singleton;

    //提供一个静态的公有方法,当使用到该方法时,才会去创建 instance
    public static Singleton getInstance(){

        fiif (singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

public class SingletonDemo {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);

    }
}
写法分析

优势

起到了 lady loading 的效果,不会造成内存浪费,实例使用到的时候,调用getInstance()方法才会创建

劣势

只能在单线程下使用,多线程会出现线程不安全的问题

试想,如果在多线程下,一个线程进入了 if (singleton == null){ ...} 判断语句块,还没来得及往下执行,另一个线程进行判断,此时实例还未来得及创建,也通过了这个判断语句,这时便会产生多个实例,出现了线程不安全问题

所以,在实际开发中,不建议使用这种方式

懒汉式(同步方法)

针对上面线程不安全的问题,我们在懒汉式的代码中,加入同步处理,第一种就是同步方法,相关代码如下

//懒汉式 同步方法
class Singleton{

    //构造器私有化
    private Singleton(){

    }

    //类的内部创建对象
    public static Singleton singleton;

    //对外提供公共的静态方法,加入同步处理
    public static synchronized Singleton getInstance(){

        if (singleton == null){
            singleton = new Singleton();  
        }
        return singleton;
    }
}

public class SingletonDemo02 {
    public static void main(String[] args) {

        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
    }
}
写法分析

首先,这种写法,由于加入了同步处理,解决了线程不安全的问题

但是我们说,这样效率很低,每个线程在想获得类的实例的时候,执行 getInstance() 方法都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面的想获得该类的实例,直接return就好了,方法进行同步,效率很低

所以,在实际开发中,也不推荐使用这种方式

懒汉式(同步代码块)

那既然方法进行同步,效率很低,我们可不可以使用同步代码块的方式呢?大家看如下的示例代码

class Singleton{

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

    //类的内部创建对象
    public static Singleton singleton;

    //对外提供公共的静态方法
    public static Singleton getInstance(){

        if (singleton == null){

            synchronized (Singleton.class){
                singleton = new Singleton();  
            }

        }
        return singleton;
    }
}
写法分析

这种写法就是,本意是想对第四种实现方式的改进,在创建实例对象的时候,进行同步,那大家思考一下,这样可行嘛?可以保证线程安全嘛


但其实,如果在多线程下,一个线程进入了 if (singleton == null){ ...} 判断语句块,还没来得及往下执行,另一个线程进行判断,此时实例还未来得及创建,也通过了这个判断语句,便会产生多个实例,所以在里面创建实例对象的时候,进行同步没有实际意义,在实际开发中,不能使用这种方式

下节预告

OK,由于篇幅的限制,本节内容就先到这里,下一节,我们接着来聊单例模式的后三种写法,包括双重检查机制,静态内部类,枚举,同时会带大家看下JDK源码中单例模式的应用,以及对单例模式的注意事项进行总结,最后,希望大家在学习的过程中,能够感觉到设计模式的有趣之处,高效而愉快的学习,那我们下期见~

相关文章
|
1月前
|
设计模式 存储 SQL
PHP中的设计模式:单例模式的探索
在PHP开发中,单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。本文将通过一个简单的例子,逐步引导你理解如何在PHP中实现和利用单例模式,以及它在实际项目中的应用价值。
|
1月前
|
设计模式 存储 负载均衡
【五】设计模式~~~创建型模式~~~单例模式(Java)
文章详细介绍了单例模式(Singleton Pattern),这是一种确保一个类只有一个实例,并提供全局访问点的设计模式。文中通过Windows任务管理器的例子阐述了单例模式的动机,解释了如何通过私有构造函数、静态私有成员变量和公有静态方法实现单例模式。接着,通过负载均衡器的案例展示了单例模式的应用,并讨论了单例模式的优点、缺点以及适用场景。最后,文章还探讨了饿汉式和懒汉式单例的实现方式及其比较。
【五】设计模式~~~创建型模式~~~单例模式(Java)
|
1月前
|
设计模式 安全 程序员
C#设计模式之单例模式
C#设计模式之单例模式
40 3
|
30天前
|
设计模式 SQL 缓存
Java编程中的设计模式:单例模式的深入理解与应用
【8月更文挑战第22天】 在Java的世界里,设计模式是构建可维护、可扩展和灵活的软件系统的基石。本文将深入浅出地探讨单例模式这一经典设计模式,揭示其背后的哲学思想,并通过实例演示如何在Java项目中有效运用。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇洞悉软件设计深层逻辑的大门。
26 0
|
1月前
|
设计模式 存储 数据库连接
Python设计模式:巧用元类创建单例模式!
Python设计模式:巧用元类创建单例模式!
32 0
|
1月前
|
设计模式 安全 测试技术
[设计模式]创建型模式-单例模式
[设计模式]创建型模式-单例模式
|
2月前
|
设计模式 安全 C++
C++一分钟之-C++中的设计模式:单例模式
【7月更文挑战第13天】单例模式确保类只有一个实例,提供全局访问。C++中的实现涉及线程安全和生命周期管理。基础实现使用静态成员,但在多线程环境下可能导致多个实例。为解决此问题,采用双重检查锁定和`std::mutex`保证安全。使用`std::unique_ptr`管理生命周期,防止析构异常和内存泄漏。理解和正确应用单例模式能提升软件的效率与可维护性。
32 2
|
2月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
60 1
|
2月前
|
设计模式 安全 Java
技术成神之路:设计模式(一)单例模式
【7月更文挑战第3天】技术成神之路:设计模式(一)单例模式
33 1
|
2月前
|
设计模式 数据采集 监控
iLogtail设计模式问题之单例模式主要适用于什么场景
iLogtail设计模式问题之单例模式主要适用于什么场景