深度学习设计模式之单例模式

一、单例模式简介

一个类只能有一个实例,提供该实例的全局访问点;

二、单例模式实现步骤

使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
在这里插入图片描述

三、单例模式的两种方式

在这里插入图片描述

1.懒汉模式

懒汉模式,通俗来讲就是只有饿的时候,才会去找饭吃。通常只有对象被需要的时候才会去创建。最显而易见的优点就是,节省资源。如果没有地方用到这个类,这个类将不会进行实例化。

1.1 简易版懒汉模式

public class LzaySingleton {

    /**
     * 成员变量
     */
    private static LzaySingleton lzaySingleton;

    /**
     * 构造方法私有化
     */
    private LzaySingleton(){

    }

    /**
     * 获取单例对象
     * @return
     */
    public static LzaySingleton  getInstance(){
        if(lzaySingleton == null){
            System.out.println("创建实例");
            lzaySingleton =  new LzaySingleton();
            return lzaySingleton;
        }
        System.out.println("实例对象已存在,无需再创建");
        return lzaySingleton;
    }

}

测试类

    public static void main(String[] args) {
        // 先创建一个对象,看是否有输出
        LzaySingleton lzaySingleton = LzaySingleton.getInstance();

        LzaySingleton lzaySingleton1 = LzaySingleton.getInstance();

    }

结果:
在这里插入图片描述
简易版的单例模式存在的问题就是:在多线程的情况下是不安全的,会打破单例的定义。
例如:有2个线程,线程A,线程B;同时成员变量lzaySingleton为null;线程A,线程B,同时走到if(lzaySingleton == null),那将会执行两次lzaySingleton = new LzaySingleton();就会实例化两次对象,从而打破单例模式的设定。
在这里插入图片描述
怎么解决呢?接下来就是我们另外一种懒汉单例模式登场了。

1.2线程安全的单例模式

怎么解决线程安全?那就很简单了,加锁就可以了。
只需要再getInstance()方法上加 synchronized就行,这样保证同一个时间点,只会有一个线程进入到这个方法,从而解决多次创建实例的问题。

public class LzaySingleton {

    /**
     * 成员变量
     */
    private static LzaySingleton lzaySingleton;

    /**
     * 构造方法私有化
     */
    private LzaySingleton(){

    }

    /**
     * 获取单例对象
     * @return
     */
    public static synchronized LzaySingleton  getInstance(){
        if(lzaySingleton == null){
            System.out.println("创建实例");
            lzaySingleton =  new LzaySingleton();
            return lzaySingleton;
        }
        System.out.println("实例对象已存在,无需再创建");
        return lzaySingleton;
    }

}

以上的方法虽然可以解决多线程的问题,但是往往单例对象的内容逻辑是非常复杂的,使用synchronized 修饰方法,当其他线程进入该方法的时候,就会进入等待,对性能还是有一定影响的。
解决这个问题,可以灵活的使用synchronized

1.3 线程安全的单例模式V2.0版

为了解决synchronized修饰方法带来的系统开销。我们可以通过灵活运用synchronized来解决此问题。众所周知synchronized 加锁是有多种方式的。我们使用代码块的方式,只有再创建对象的时候使用 synchronized

public class LzaySingleton {

    /**
     * 成员变量
     */
    private static LzaySingleton lzaySingleton;

    /**
     * 构造方法私有化
     */
    private LzaySingleton(){

    }

    /**
     * 获取单例对象
     * @return
     */
    public static LzaySingleton  getInstance(){
        if(lzaySingleton == null){
        	//  synchronized 代码块
            synchronized (LzaySingleton.class){
                System.out.println("创建实例");
                lzaySingleton =  new LzaySingleton();
            }
            return lzaySingleton;
        }
        System.out.println("实例对象已存在,无需再创建");
        return lzaySingleton;
    }

}

这种方式虽然解决了,锁粒度问题带来的性能开销问题,但是又有一个致命问题,我们又回到解放前了。
同样的多线程问题,如果线程A,线程B,同时又到了这一步:
在这里插入图片描述
线程A和B拿到的对象都是null,然后线程A侥幸拿到了锁,线程B就只能再外面等待线程A。同样的问题就会再现,线程A执行完lzaySingleton = new LzaySingleton();线程B就会拿到锁,然后再执行一次lzaySingleton = new LzaySingleton();,所有使用synchronized 代码块的方式加锁,还不够完善。

1.3 线程安全的单例模式V2.1版-双重校验锁

因为上面使用了synchronized 代码块的方式加锁,减少了系统的开销,但是也带来了新的问题,因此我们多增加一个判断,如下:

public class LzaySingleton {

    /**
     * 成员变量
     */
    private static LzaySingleton lzaySingleton;

    /**
     * 构造方法私有化
     */
    private LzaySingleton(){

    }

    /**
     * 获取单例对象
     * @return
     */
    public static LzaySingleton  getInstance(){
        if(lzaySingleton == null){
            synchronized (LzaySingleton.class){
                if(lzaySingleton == null){
                    System.out.println("创建实例");
                    lzaySingleton =  new LzaySingleton();
                }
            }
            return lzaySingleton;
        }
        System.out.println("实例对象已存在,无需再创建");
        return lzaySingleton;
    }

}

这样,即使线程A和线程B同时都到了这一步:
在这里插入图片描述
即使A拿到了锁,执行完lzaySingleton = new LzaySingleton();以后,到B执行时也会被这个校验给拦住
在这里插入图片描述
至此高性能加锁的单例模式完成,但是他还不是最终版本,依旧存在一些小问题。

1.3 线程安全的单例模式V3.0版-双重校验锁终极版本

目前代码层面已经解决问题,但是深究底层,时 lzaySingleton = new LzaySingleton();这个操作并不是原子性的,因为底层在编译运行代码的时候,会对当前代码进行优化,会存在指令重排序情况。而 new LzaySingleton() 时至少需要3步才能完成。
1.分配内存空间;
2.实例化对象;
3.将对象指向分配的空间地址;
如果编译的时候进行了指令重排序,本来正常操作时 1 -> 2 -> 3这样,重排序后则可能会出现 1-> 3 -> 2 这个时候,单线程肯定没问题,但是在多线程的情况下,因为对象还没创建完成,其他线程执行到这里的时候,认为对象不为空,已经实例化成功了,就直接获取对象使用了。其实拿到的对象并不是最终的对象,只是一个半成品的,所以使用的过程中,就会出现意想不到的问题。
在这里插入图片描述
这个时候就需要使用 JVM的关键字 volatile 来解决指令重排序的问题了。
简单介绍一下 volatile
1.volatile有3个特性:可见性、有序性、原子性;
可见性是当多个线程同时访问一个变量的时候,其中一个线程修改了变量的值,其他线程能立刻看到修改的变量值。
有序性是禁止了指令重排序,执行程序代码时,按照顺序来执行。
原子性是一个操作是不能中断的,要不全部都执行,要不都不执行。
2. volatile 是用来修饰变量的,无法修饰代码块和方法。
3. volatile的使用:只要修饰一个 可能被多线程同时访问的变量上就行。
详细情况可自行查询相关资料。

最终代码如下: 对成员变量lzaySingleton 进行了volatile 修饰,防止了创建时的指令重排序。

public class LzaySingleton {

    /**
     * 成员变量
     */
    private static volatile LzaySingleton lzaySingleton;

    /**
     * 构造方法私有化
     */
    private LzaySingleton(){

    }

    /**
     * 获取单例对象
     * @return
     */
    public static LzaySingleton  getInstance(){
        if(lzaySingleton == null){
            synchronized (LzaySingleton.class){
                if(lzaySingleton == null){
                    System.out.println("创建实例");
                    lzaySingleton =  new LzaySingleton();
                }
            }
            return lzaySingleton;
        }
        System.out.println("实例对象已存在,无需再创建");
        return lzaySingleton;
    }

}

至此单例模式的懒汉模式最终版完成。

2.饿汉模式

饿汉模式相对来说,比较简单,通俗来说就是,一上来就先去找吃的和懒汉相反。系统加载的时候就初始化对象。优点就是简单,不存在什么多线程问题。缺点就是占用内存。
实现如下:

public class HungrySingleton {
    // 一开始就初始化对象
    private static HungrySingleton hungrySingleton = new HungrySingleton();
    // 私有化构造方法
    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

四、扩展实现单例

1.使用枚举的方式实现单例模式

使用枚举的方式实现单例模式,是《Effective java》一书中提到的
在这里插入图片描述
上面的几种方式已经实现了单例模式,但是如果碰到特殊的情况,比如反射的时候,通过 setAccessible() 方法还是可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象的。使用枚举就天然的解决反射问题。

直接在枚举类里面写功能方法,代码如下:

public enum SingletonEnum {

    INSTANCE
    ;
    public void test(){
        System.out.println("1111");
    }
}

测试类

   public static void main(String[] args) {
        SingletonEnum.INSTANCE.test();

    }

结果
在这里插入图片描述

2.使用内部类的方式实现单例模式

内部类的方式实现单例模式,加载Singleton的时候静态内部类 SingletonHolder 不会被加载。 只有调用 getInstance()方法的时候才会去初始化对象。这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。
以下是代码实现:

/**
 * 静态内部类方式实现单例
 */
public class Singleton {

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

    public void test(){
        System.out.println("2222");
    }
    /**
     * 静态内部类
     */
    private static class SingletonHolder{
        // 初始化对象
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }

}

测试类

    public static void main(String[] args) {
        Singleton.getInstance().test();

    }

结果
在这里插入图片描述

FAQ

1.为什么要私有化构造方法?

单例模式主要特点就是保证对象只被实例化一次,所以构造方法的私有化,才能保证不能随便的去new() 对象,从而保证对象只能初始化一次。

2.为什么成员变量要用 static 修饰?

程序调用类中方法只有两种方式,①创建类的一个对象,用该对象去调用类中方法;②使用类名直接调用类中方法,格式“类名.方法名()”;
现在没有办法new 对象了,所以只能使用第二种方式。
java中静态方法没有办法调用非静态的类或者变量,所以成员变量也需要使用static来修饰。

3.单例模式的应用场景?

  • 数据库连接池:数据库连接池是一个重要的资源,单例模式可以确保应用程序中只有一个数据库连接池实例,避免资源浪费。
  • 配置文件管理器:应用程序通常需要一个配置文件管理器来管理配置文件,单例模式可以确保在整个应用程序中只有一个这样的实例。
  • 缓存系统:缓存系统是提高应用程序性能的重要组件,单例模式可以确保只有一个缓存实例。

4.单例模式使用的注意情况

单例模式主要分为 懒汉 和饿汉,我们通常再使用的时候要综合评估两种方式的优缺点,决定使用,比如:对于一些占用内存小的类我们使用饿汉模式,占用内存较大的类我们就使用懒汉模式。一开始就需要加载的并且会被频繁使用的就用饿汉模式。

5.JDK中的单例

java.lang.Runtime类使用的就是单例模式(饿汉),这个类是运行时的类,很多信息需要获取所以使用的是饿汉单例模式,如下:
在这里插入图片描述
java.awt.Desktop类使用的是懒汉单例模式:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/615071.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

工业机器人应用实践之玻璃涂胶(篇一)

工业机器人 工业机器人,即面向工业领域的机器人。工业机器人是广泛用于工业领域的多关节机械手或多自由度的机器装置,具有一定的自动性,可依靠自身的动力能源和控制能力实现各种工业加工制造功能。工业机器人被广泛应用于电子、物流、化工等…

使用OpenCV实现图像平移

使用OpenCV实现图像平移 程序流程效果代码 程序流程 读取图像并获取其高度、宽度和通道数。定义平移量tx和ty,并创建平移矩阵M。使用cv2.warpAffine函数对图像进行仿射变换(平移),得到平移后的图像。显示平移后的图像。等待用户按…

HTML【常用的标签】、CSS【选择器】

day45 HTML 继day44,w3cschool 常用的标签 k) 表格 表格由 table 标签来定义。每个表格均有若干行(由 tr 标签定义),每行被分割为若干单元格(由 标签定义)。字母 td指表格数据(table data&…

VSCode:设置顶部文件标签页滚动条的宽度

使用VSCode打开多个文件后,顶部的文件标签可以通过滚动条进行滚动,但是缺点是该滚动条太窄了,不好选择。 可以通过如下方法修改改滚动条的宽度: 1.点击设置 2.选择工作台->编辑管理->Title Scrollbar Sizing->Large 3.可…

QJ71E71-100 三菱Q系列以太网通信模块

QJ71E71-100 三菱Q系列以太网通信模块 QJ71E71-100以太网模块是PLC侧连接Q系列PLC与本站系统的接口模块,如个人计算机和工作站,也是通过以太网使用TCP/IP或UDP/IP通讯协议在 PLC 之间的接口模块。QJ71E71-100外部连接,QJ71E71-100参数规格,QJ71E71-100用…

表面的相似,本质的不同

韩信与韩王信,两个韩信的结局都是被刘邦所杀,似乎结局类似。但是,略加分析,就会发现其中存在本质的区别。 韩信属于必杀。他的王位是要来的,有居功自傲的本意,功高震主而且毫不避讳。而且年轻,…

linux上使用mariadb安装mysql环境

之前都是手动安装mysql数据库,现在尝试下在线安装,为后面的项目部署做准备,突然发现使用mariadb安装mysql环境真的超级简单。 1.使用mariadb安装mysql 安装服务端: yum install mariadb-server -y 安装客户端: yum i…

C++(week3):C语言文件操作

文章目录 (十二) 文件1.流(1)流模型(2)程序员视角的文件(3)缓冲区类型(4)标准流(5)二进制文件 与 文本文件(6)文件流的接口(API) 2.打开/关闭文件(1)fopen(2)fclose(3)示例代码 3.读/写文件(1)fgetc / fputc:一个字符一个字符地读写(2)fgets / fputs:一行…

UIKit之UIButton

功能需求: 点击按钮切换按钮的文字和背景图片,同时点击上下左右可以移动图片位置,点击加或减可以放大或缩小图片。 分析: 实现一个UIView的子类即可,该子类包含多个按钮。 实现步骤: 使用OC语言&#xf…

【碳化硅】陷阱(traps)对SiC MOSFET阈值电压漂移的影响

这篇文章是关于硅碳化物(SiC)金属氧化物半导体场效应晶体管(MOSFET)的阈值电压漂移问题的研究。文章的主要目的是通过研究不同的陷阱(traps)对阈值电压漂移的影响,来解决SiC MOSFET的可靠性问题。 摘要(Abstract) 文章提出了一种研究方法,用于分析影响SiC MOSFET阈值…

YUV中Y颜色模型的采样

YUV的特点 相对于表示颜色的GUI, YUI将亮度(用Y表示)与色调(用U和V表示)分开来表示。又因为人类视网膜上的视网膜杆细胞要多于视网膜锥细 胞,说得通俗一些,视网膜杆细胞的作用就是识别亮度&…

【Delphi 爬虫库 6】使用正则表达式提取猫眼电影排行榜top100

正则表达式库的简单介绍 正则表达式易于使用,功能强大,可用于复杂的搜索和替换以及基于模板的文本检查。这对于输入形式的用户输入验证特别有用-验证电子邮件地址等。您还可以从网页或文档中提取电话号码,邮政编码等,在日志文件中…

Tiff文件解析和PackBits解压缩

实现了Tiff图片文件格式的解析,对Tiff文件中的PackBits压缩格式进行解压缩,对Tiff文件中每一个Frame转换成BufferedImage显示。 Java语言实现,Eclipse下开发,AWT显示图片。 public static TIFF Parse(final byte[] bytes) throw…

【Rollup】用rollup从0到1开发一个js插件并发布到npm

Rollup 是一个 JavaScript 模块打包器,专注于打包 ES6 模块将其编译回多种模块化格式,尤其适合打包库和框架,因为它可以生成更小、更高效的代码,并且特别适合将代码打包成可在浏览器中使用的库。 从0到1开发js插件 1.创建文件夹…

解决docker安装Wordpress速度过慢的问题

先可以在dockerhub上查看Wordpress的详情: Dockerhttps://hub.docker.com/search?qwordpress 具体速度慢的问题如下: 现在打开docker右上角的设置图标,并进入docker engine,添加如下代码: "registry-mirrors&…

贪心算法----摆动序列

今日题目:leetcode376 点击跳转题目 观察样例2: 发现最长摆动序列都是极大值和极小值 再加上两个端点,那么我们保证每次都能选择到每个极值点,就能从局部最优推广全局最优了! 但是还有一些细节情况需要注意&#xff…

社工库信息查询

此网站需要注册账号,新用户注册送3点券,每日签到可获得1.5点券。也可通过充值来查 我这里有方法可以利用缺陷来无限获取点券查人

土地档案管理关系参考论文(论文 + 源码)

【免费】javaEE土地档案管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89296786 土地档案管理关系 摘 要 研究土地档案管理关系即为实现一个土地档案管理系统。土地档案管理系统是将现有的历史纸质档案资料进行数字化加工处理,建成标准化的…

LoRa模块学习

什么是LoRa调制 LoRa(Long Range,远距离)是一种调制技术,与同类技术相比,提供更长的通信距离。调制是基于扩频技术,线性调制扩频(CSS)的一个变种,具有前向纠错&#xff…

C++数据结构之链表树图的存储

本文主要介绍用数组存储,结构只做简单介绍 目录 文章目录 前言 结构体实现 1、链表的存储 2、树的存储 3、图的存储 数组实现 1、链表实现 2、树和图的实现 总结 前言 在正常工程中,我们通常使用结构体或者类,来定义并使用如链表…