单例模式的简单理解

单例模式


前言

设计模式是将一些经典的问题场景进行整合归纳,并提供一些解决方案,相当于一种"套路"。

熟练掌握设计模式,可以提高代码的下限。


一、单例模式是什么

单例模式,简单来说就是单个实例。即在整个进程中的某个类有且只有一个对象

要满足这种条件,需要从根本上保证对象是唯一实例。 只通过人的自我意识是不行的,同时需要通过编码上的技巧使编译器可以检测代码中是否有多个实例。在发现存在多个实例时,计算机会编译出错。

二、单例模式的使用

饿汉模式

饿汉模式是指在运行之后,无论进程是否需要该对象都在最开始的时候创建。

  • 在类Singleton中,首先初始化类对象instance,static成员在类加载的时候初始化。
    当后续需要使用这个类的实例时,就可以调用getInstance方法进行获取了。
  • 同时我们还需要禁止外部代码创建该类的实例,当类之外的代码尝试new的时候,就需要调用构造方法,因此我们将构造方法设为private. 如此依赖在new的时候就会报错。
java 复制代码
class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return instance;
    }
}

我们可以在main方法中进行判断,结果为true

java 复制代码
public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();

        Singleton s3 = Singleton.getInstance();
        System.out.println(s1 == s3);
    }

单线程下的懒汉模式

懒汉模式与饿汉模式相反,它不在进程启动时就创建实例,而是在第一次使用的时候才去创建。

  • 与懒汉模式相同,将构造方法设为private防止类之外代码创建新对象

  • 由于初始对象lazy = null 因此在调用getLazy()方法的过程中,需要对lazy对象进行是否非空判断。

    代码示例如下

java 复制代码
class SingletonLazy {
	private static SingletonLazy lazy = null;
    public static SingletonLazy getLazy() {
        if (lazy == null) {
            lazy = new SingletonLazy();
        }
        return lazy;
    }
}

多线程下的懒汉模式(优化懒汉模式)

在多线程中,如果多个线程要同时调用getLazy()方法,又会发生什么问题呢?

我们知道,getLazy()中的指令并不是单个/原子指令的。
多线程中的分析 :我们可以通过时间轴进行一个简单的模拟。如图所示,因为线程是并发执行的,当线程A初步进行判断且lazy = null时,线程B将整个方法执行完了,此时lazy != null 。

而线程A的getLazy()方法还没执行完,因此在线程A中又new了一个lazy对象。

于是我们可以想到,通过加锁操作保证线程安全。

加锁

针对该线程安全问题所进行的加锁操作如下,通过加锁将if和new打包成一个原子操作,如此一来就能解决线程安全问题了。

java 复制代码
public static SingletonLazy getLazy() {
            synchronized (lazy){
                if (lazy == null) {
                    lazy = new SingletonLazy();
                }
            }
        return lazy;
    }

在上述代码中,每次调用该类对象都需要进行一次锁操作的判定,一个两个线程还好,如果是多个线程的话,锁判定操作无疑是很消耗cpu资源的。
我们可以再次进行优化。

首先我们初步判断是否要进行加锁操作,如果需要,我们再加锁,若是不需要加锁,就代表着lazy != null 我们就可以直接跳过加锁操作了。

在加入这段代码之后的很长一段时间,lazy对象是非空的,这可能会出现内存可见性的问题,因此我们可以加上volatile解决这个问题。

优化代码如下

java 复制代码
class SingletonLazy {
	//设对象lazy为空
    private volatile static SingletonLazy lazy = null;
	
    public static SingletonLazy getLazy() {
        if (lazy == null) {
    		//条件判断是否需要加锁 如果lazy= null 
            synchronized (lazy) {
                if (lazy == null){
                //条件判断 是否需要new
                    lazy = new SingletonLazy();
                }
            }
        }
        return lazy;
    }

    private SingletonLazy() {

    }
}

三、总结

饿汉模式为了"急",在一开始就创建。那么当一个代码中存在多个单例类时,就会导致这些实例在启动时集体创建,会拖慢程序启动时间。

懒汉模式在首次调用时才会创建类对象,如此一来拖慢程序的时间分散,用户难以察觉"卡顿"。

关于内存可见性的文章 ☞内存可见性

关于本文源码 ☞单例模式简单使用

相关推荐
w_31234543 分钟前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安6 分钟前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA9 分钟前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_192849990616 分钟前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟33 分钟前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S44 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos1 小时前
c++------------------函数
开发语言·c++
yuanbenshidiaos1 小时前
C++----------函数的调用机制
java·c++·算法
程序员_三木1 小时前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴