多线程代码设计模式之单例模式

目录

设计模式引入

饿汉模式

懒汉模式

单例模式总结


设计模式引入

1.1.什么是设计模式

(1)设计模式就是一种代码的套用模板。例如:一类题型的步骤分别有哪些,是可以直接套用的。

(2)像棋谱,也是一种设计模式。根据棋谱去下棋,棋术自然不会很差。

(3)官方定义就是说:设计模式就是一套经过反复使用、多人知晓、经过分类、代码设计经验的总结

(4)使用代码设计模式可以提高代码的下限。更容易被人理解,使用等等

本文我们主要介绍设计模式中的单例设计模式

1.2.单例设计模式

(1)单例模式,就是只有单个对象。

(2)单例模式,在整个代码进程中的某个类,有且只会产生一个对象,也不会多new出来前提的对象(这个类就是单例模式,它只会产生一个对象)

(3)如何保证只会有一个对象呢?那就需要程序员通过代码去设计,用代码去限制和规范。

(4)根本上可以保证对象是唯一的,这种设计模式就称为单例模式

(5)单例模式,本节内容介绍两种最常用的:饿汉模式和懒汉模式

饿汉模式

1.1.概念引入

(1)什么是饿汉模式?听名字就是一个很饿的汉子

(2)饿汉模式,就是在类加载的时候,就完成了对象的实例化

(3)我们把迫不及待的实例化对象这种行为,称为饿汉模式

1.2.代码设计

(1)类体

这个名字是可以随便起的 

class Singleton {
  
}

(2)实例化对象

这里直接实例化一个饿汉对象,赋值给instance引用。这个引用是private static类型的,外部访问不到,而且在类加载的时候,就把对象给初始化好了。 

class Singleton {
    private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉
}

(3)提供获取对象的方法

class Singleton {
    private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉
    //用于外部获取饿汉对象
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton() {
        //私有构造方法,外部无法再进实例化
    }
}

外部通过调用这个方法,就可以拿到对象的一个引用;多次调用改方法,获得的对象都是同一份。 

(4)私有构造方法

这是最关键的一步,将构造方法设置为私有的,外部就无法在new对象了。下面这也是“饿汉模式”的完整代码了

class Singleton {
    private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉
    //用于外部获取饿汉对象
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton() {
        //私有构造方法,外部无法再进实例化
    }
}

(5)验证是否同一个对象

 public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }

我们发现,这两个对象都是一样的。

(6)验证外部不可new

以上就饿汉模式代码设计的全部了,还有一些小问题,下面接着介绍。

1.3.线程安全等问题

(1)线程安全问题

在多线程代码中,对于饿汉设计模式的代码,是线程安全的。

原因:饿汉模式在加载类的时候,对象就已经实例化好了,比线程通过get方法去获得改对象的引用更快;后续线程获取对象的时候,都是获取到的同一个变量。

(2)保证唯一对象问题

初心:类似饿汉模式的代码设计,都是提供给外部使用的,外部是无法new对象的;即使内部可以,但是设计者不会那么蠢。

外部new对象怎么办:外部是可以通过反射的方式再new一次对象,但是这种方式我们不考虑,反射本身的开销和成本也很大。就像有人下定决心要偷你家东西,你也无法防住。

懒汉模式

1.1.概念引入

(1)听名字,看似懒,其实是高效率

(2)懒汉模式:类很懒,当程序员需要对象而去调用时,它才会实例化对象

(3)我们把这种不着急实例化对象的,称为“懒汉模式”

1.2.代码设计 

(1)类体

老样子,名字随意 

class SingletonLazy {
  
}

(2)对象的引用

因为很懒,所以一开始是不实例化对象的 

class SingletonLazy {
    private static SingletonLazy instance = null;//一开始不初始化对象
   
}

(3)对外开放对象

只有在第一次调用改方法时,才会去实例化一个对象 

class SingletonLazy {
    private static SingletonLazy instance = null;//一开始不初始化对象
    public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
         if(instance == null) {
             instance = new SingletonLazy();
        }
        return instance;
    }

}

(4)关闭构造方法

只有这样,外部才没有办法去new对象 

class SingletonLazy {
   private static SingletonLazy instance = null;//一开始不初始化对象
    public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
         if(instance == null) {
             instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy() {

    }
}

上面就是一个和饿汉模式类似的代码结构了

(5)同一个对象

 public static void main(String[] args) {
        SingletonLazy s1  = SingletonLazy.getInstance();
        SingletonLazy s2  = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }

(6)外部不可new

这里打断一下,上面的代码并不完整,因为上面的代码是一个线程不安全的代码。

线程安全的完整代码:接下来介绍

class SingletonLazy {
    private static volatile SingletonLazy instance = null;//一开始不初始化对象
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
        if(instance == null) {
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() {

    }
}
1.3.线程安全问题

(1)为什么不安全?

在类加载完之后,并没有实例化出对象;此时两个线程去调用getInstance()方法,就很大可能会创造出两个对象,这就违背了一个对象原则了。

(2)为什么会有线程不安全问题?

我们从指令的执行和线程调度方面切入分析。下面都是按照发生线程安全问题的情况下

1)有两个线程t1、t2,先后调用了改方法,并且下面的条件都成立,并且进入

if(instance == null)

像这样先后进入if语句之后,他们就会创造出两个实例,这就是线程不安全的问题。

上面产生的问题就是由于操作不是原子性造成的,我们只需要进行加锁操作就好。 

1.4.设计线程安全代码

(1)加锁之后

private static Object locker = new Object();
public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
     synchronized (locker) {
         if(instance == null) {
              instance = new SingletonLazy();  
         }
     }
    return instance;
}

加锁之后,同一个时间就只会有一个线程进入if语句并且实例化对象,当该线程解锁之后,对象已经创造好了;此时第二个线程不再阻塞,并进入if语句,但是此时条件已经不成立便不会进入。此时,就不会由于多线程代码而产生问题了。

(2)判断是否要加锁

上面的代码产生线程安全问题的主要原因是:由于对象没有实例化好,但是,当对象实例化好之后再去调用该方法,是不会产生任何线程问题的。此时,锁就现得很笨重,因此,我们只需要让第一次调用时加锁就好,于是得出下面的改进代码

private static Object locker = new Object();
public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
        if(instance == null) {
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
}

这就是双重if判断,但是两个if语句发挥的作用不一样。

第一个if:判断是否要加锁

第二个if:判断是否要创建对象

(3)预防指令重排序

上述代码就是最终的安全代码了吗?不,还不是,还会存在一个问题,那就是指令重排序造成的线程安全问题。

instance = new SingletonLazy();

new对象这个代码大概可以分成三步

1.申请内存空间

2.调用构造方法(对内存空间进行初始化)

3.把此时内存空间的地址,赋值给instance引用

指令执行的顺序大概有两种:123或者132。在单线程中,两种顺序都不会出现问题,但是在多线程中,132的顺序是会出现问题的。

问题:当执行的顺序是132时,t1线程执行完3之后,也就是赋值给了instance引用,此时不为空,然后被调度走,t2线程就可以返回这个引用,此时这个对象内部是没有初始化的,里面的值都是0,t2线程拿着这个引用去做一些事情,就会引起一些问题。

所以,我们要加上volatile关键字。

private static volatile SingletonLazy instance = null;//一开始不初始化对象

可见,volatile关键字表面上是预防内存可见性问题,更深的是预防指令重排序问题。给变量加上,可以预防对该变量发生一下指令重排序,可以保证intance引用存放的是完整对象。

线程安全的懒汉模式代码:

class SingletonLazy {
    private static volatile SingletonLazy instance = null;//一开始不初始化对象
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象
        if(instance == null) {
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
               }
            }
        }
        return instance;
    }
    private SingletonLazy() {
    }
}

单例模式总结

1.单例模式引用场景

(1)在实际的业务中,有的类,只需要有一个对象就够了

(2)比如,要写一个类,用来实现一些功能,可以加载上百G的数据。所以这个类只需要创造一个对象,就可以管理完这些数据了。如果是多个实例,消耗的内存也更加大。

2.懒汉模式的优势

(1)在程序加载中,如果有多个单例模式且是饿汉模式,那么在加载的时候就需要花费很多的时间。

(2)如果是懒汉模式,在程序加载的时候不会一下子创造出很多的对象,因此,时间效率就得到了提高。


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

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

相关文章

java对象是怎么在jvm中new出来的,在内存中查看java对象成员变量字段属性值

java对象是怎么在jvm中new出来的 查看java对象字段属性在内存中的值 java 对象 创建 流程 附上java源码 public class MiDept {private int innerFiled999;public MiDept() {System.out.println("new MiDept--------------");}public String show(int data) {Sy…

Python学习之-魔术方法

前言: Python 中的魔术方法(Magic Methods),也称作特殊方法(Special Methods),是那些被双下划线包围的方法,例如 init。这些方法在 Python 中有特殊的含义,它们并不需要…

(免费分享)基于springboot,vue问卷调查系统

用户注册、用户登录、创建调查问卷、编辑问卷问题和选型(支持题型:单选、多选、单行文本、多行文本、数字、评分、日期、文本描述)、保存和发布问卷、停止问卷调查、游客填写调查问卷(一个IP地址只能填写一次) 技术&a…

Adobe After Effects 2024 v24.3 (macOS, Windows) - 后期特效

Adobe After Effects 2024 v24.3 (macOS, Windows) - 后期特效 Acrobat、After Effects、Animate、Audition、Bridge、Character Animator、Dimension、Dreamweaver、Illustrator、InCopy、InDesign、Lightroom Classic、Media Encoder、Photoshop、Premiere Pro、Adobe XD 请…

支持编写任何类型的爬虫:基于 Golang 的优雅爬虫框架 | 开源日报 No.216

gocolly/colly Stars: 21.5k License: Apache-2.0 colly 是 Golang 的优雅爬虫和爬虫框架。 该项目提供了一个清晰的接口,用于编写任何类型的爬虫/抓取器/蜘蛛。Colly 可以轻松从网站中提取结构化数据,可用于数据挖掘、数据处理或存档等各种应用。 其主…

如何把学浪app的视频保存本地

如何把学浪app里面的视频保存到本地,其实很简单,只需要用到一个工具,那就是小浪助手.exe 这里我已经把小浪助手.exe打包好了,有需要得话自己下载 链接:https://pan.baidu.com/s/1y7vcqILToULrYApxfEzj_Q?pwdkqvj 提…

在线视频教育平台|基于Springboot的在线视频教育平台系统设计与实现(源码+数据库+文档)

在线视频教育平台目录 基于Springboot的在线视频教育平台系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、前台: 2、后台 用户功能模块 教师功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&a…

如何在Python中将HTML实体代码转换为文本

在处理HTML数据时&#xff0c;有时会遇到HTML实体代码&#xff0c;这些代码是为了在HTML中表示特殊字符而使用的。例如&#xff0c;<表示小于符号(<)&#xff0c;>表示大于符号(>)&#xff0c;&表示和符号(&)等等。那么当我们在实际操作中可能会遇到下面的…

Centos7使用docker安装Jenkins(含pipeline脚本语句)

一、下载Jenkins docker pull jenkins/jenkins:lts 二、启动Jenkins docker run \-u root \--rm \-d \-p 8081:8080 \-p 50000:50000 \-v /root/docker/jenkins/var/jenkins_home:/var/jenkins_home \-v /var/run/docker.sock:/var/run/docker.sock \-v /usr/bin/docker:/usr…

初学者也能轻松使用的原型设计工具

原型是之前所有 UX 设计工作的合并&#xff0c;是一种单一、可视、功能的产品&#xff0c;用于验证假设和测试设计。作为产品经理或设计师&#xff0c;原型设计工具是必不可少的合作伙伴。目前网站原型设计中可以使用的工具有很多&#xff0c;比如 Axure、Sketch、XD、Figma 等…

Vue2 —— 学习(一)

目录 一、了解 Vue &#xff08;一&#xff09;介绍 &#xff08;二&#xff09;Vue 特点 &#xff08;三&#xff09;Vue 网站 1.学习&#xff1a; 2.生态系统&#xff1a; 3.团队 二、搭建 Vue 开发环境 &#xff08;一&#xff09;安装与引入 Vue 1.直接引入 2.N…

微信小程序认证,备案,域名,证书,上线全流程

1.微信公众平台完成小程序认证和备案。 配置服务类目&#xff1a; 2.购买域名并完成域名实名认证和备案&#xff0c;公安备案。 3.购买https证书。 下载证书&#xff1a; 4.创建目录 mkdir -p /home/app/exam/ssl。上传证书到该目录下。 5.创建nginx配置文件: vim /usr/local…

09 Python进阶: JSON 数据解析、日期和时间

JSON 数据解析 JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。 Python3 中可以使用 json 模块来对 JSON 数据进行编解码&#xff0c;它包含了两个函数&#xff1a; json.dumps(): 对数据进行编码。 json.loads(): 对数据进行解码。 Python 编码为 JSON …

Hugging Face入门(一)

简介 本文主要内容&#xff1a; Hugging Face介绍环境搭建敲两个例子 Hugging Face介绍 Hugging Face 是一家法美合资公司&#xff0c;总部位于纽约市&#xff0c;成立于2016年。它由法国企业家Clment Delangue、Julien Chaumond和Thomas Wolf在纽约市创立&#xff0c;最初是…

精品丨PowerBI负载测试和容量规划

当选择Power BI作为业务报表平台时&#xff0c;如何判断许可证的选择是否符合业务需求&#xff0c;价格占了主导因素。 Power BI的定价是基于SKU和服务器内核决定的&#xff0c;但是很多IT的负责人都不确定自己公司业务具体需要多少。 不幸的是&#xff0c;Power BI的容量和预期…

HiveSQL如何生成连续日期剖析

HiveSQL如何生成连续日期剖析 情景假设&#xff1a; 有一结果表&#xff0c;表中有start_dt和end_dt两个字段&#xff0c;&#xff0c;想要根据开始和结束时间生成连续日期的多条数据&#xff0c;应该怎么做&#xff1f;直接上结果sql。&#xff08;为了便于演示和测试这里通过…

golang slice总结

目录 概述 一、什么是slice 二、slice的声明 三、slice的初始化、创建 make方式创建 创建一个包含指定长度的切片 创建一个指定长度和容量的切片 创建一个空切片 创建一个长度和容量都为 0 的切片 new方式创建 短声明初始化切片 通过一个数组来创建切片 声明一个 …

C++可变参数模板

可变参数模板 一个可变参数模板就是一个接受可变数目参数的模板函数或模板类。 可变数目的参数被称为参数包。 存在两种参数包&#xff1a; 模板参数类&#xff0c;表示零个或多个模板参数&#xff1b;函数参数包&#xff0c;表示零个或多个函数参数。 我们用一个省略号来…

雷弗流体创新技术装备与您与您相约2024第13届生物发酵展

参展企业介绍 保定雷弗流体科技有限公司于2010年1月成立。为创新型企业&#xff0c;荣获国家级高新技术企业、国家级专精特新小巨人企业、河北省单项冠军企业、组织部巨人计划创业团队等荣誉称号。 保定雷弗流体科技有限公司现有职工180人&#xff0c;其中工程技术人员53人。现…

GitHub入门与实践

ISBN: 978-7-115-39409-5 作者&#xff1a;【日】大塚弘记 译者&#xff1a;支鹏浩、刘斌 页数&#xff1a;255页 阅读时间&#xff1a;2023-08-05 推荐指数&#xff1a;★★★★★ 好久之前读完的了&#xff0c;一直没有写笔记。 这本入门Git的书籍还是非常推荐的&#xff0c;…