设计模式之单例设计模式
单例设计模式
- 2.1 孤独的太阳盘古开天,造日月星辰。
- 2.2 饿汉造日
- 2.3 懒汉的队伍
- 2.4 大道至简
读《秒懂设计模式总结》
单例模式(Singleton)是一种非常简单且容易理解的设计模式。顾名思义,单例即单一的实例,确切地讲就是指在某个系统中只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致。singleton一词在逻辑学中指“有且仅有一个元素的集合”,这非常恰当地概括了单例的概念,也就是“一个类仅有一个实例”。
2.1 孤独的太阳盘古开天,造日月星辰。
从“夸父逐日”到“后羿射日”,太阳对于我们的先祖一直具有着神秘的色彩与非凡的意义。随着科学的不断发展,我们逐渐揭开了太阳系的神秘面纱。我们可以把太阳系看作一个庞大的系统,其中有各种各样的对象存在,丰富多彩的实例造就了系统的美好。这个系统里的某些实例是唯一的,如我们赖以生存的恒星太阳。
与其他行星或卫星不同的是,太阳是太阳系内唯一的恒星实例,它持续提供给地球充足的阳光与能量,离开它地球就不会有今天的勃勃生机,但倘若天上有9个太阳,那么将会带来一场灾难。太阳东升西落,循环往复,不多不少仅此一例。
2.2 饿汉造日
既然太阳系里只有一个太阳,我们就需要严格把控太阳实例化的过程。我们从最简单的开始,先来写一个Sun类。请参看代码 。
public class Sun {}
太阳类Sun中目前什么都没有。接下来我们得确保任何人都不能创建太阳的实例,否则一旦程序员调用代码“new Sun()”,天空就会出现多个太阳,便又需要“后羿”去解决了。有些读者可能会疑惑,我们并没有写构造器,为什么太阳还可以被实例化呢?这是因为Java可以自动为其加上一个无参构造器。为防止太阳实例泛滥将世界再次带入灾难,我们必须禁止外部调用构造器,请参看代码
public class Sun {private void Sun(){} // 构造器私有化
}
我们在第3行将太阳类Sun的构造方法设为private,使其私有化,如此一来太阳类就被完全封闭了起来,实例化工作完全归属于内部事务,任何外部类都无权干预。既然如此,那么我们就让它自己创建自己,并使其自有永有
public class Sun {private static final Sun sun = new Sun();public Sun() {} // private constructor
}
代码第3行中“private”关键字确保太阳实例的私有性、不可见性和不可访问性;而“static”关键字确保太阳的静态性,将太阳放入内存里的静态区,在类加载的时候就初始化了,它与类同在,也就是说它是与类同时期且早于内存堆中的对象实例化的,该实例在内存中永生,内存垃圾收集器(Garbage Collector, GC)也不会对其进行回收;“final”关键字则确保这个太阳是常量、恒量,它是一颗终极的恒星,引用一旦被赋值就不能再修改;最后,“new”关键字初始化太阳类的静态实例,并赋予静态常量sun。这就是“饿汉模式”(eager initialization),即在初始阶段就主动进行实例化,并时刻保持一种渴求的状态,无论此单例是否有人使用。单例的太阳对象写好了,可一切皆是私有的,外部怎样才能访问它呢?正如同程序入口的静态方法main(),它不需要任何对象引用就能被访问,我们同样需要一个静态方法getInstance()来获取太阳的单例对象,同时将其设置为“public”以暴露给外部使用
public class Sun {private static final Sun sun = new Sun();public Sun() {} // private constructorpublic static Sun getInstance(){return sun;}
}
太阳单例类的雏形已经完成了,对外部来说只要调用Sun.getInstance()就可以得到太阳对象了,并且不管谁得到,或是得到几次,得到的都是同一个太阳实例,这样就确保了整个太阳系中恒星太阳的唯一合法性,他人无法伪造。当然,读者还可以添加其他功能方法,如发光和发热等,此处就不再赘述了。
2.3 懒汉的队伍
至此,我们已经学会了单例模式的“饿汉模式”,让太阳一开始就准备就绪,随时供应免费日光。然而,如果始终没人获取日光,那岂不是白造了太阳,一块内存区域被白白地浪费了?这正类似于商家货品滞销的情况,货架上堆放着商品却没人买,白白浪费空间。因此,商家为了降低风险,规定有些商品必须提前预订,这就是“懒汉模式”(lazy initialization)。沿着这个思路,我们继续对太阳类进行改造
public class Sun {private static Sun sun = new Sun();public Sun() {} // private constructorpublic static Sun getInstance(){if (null == sun) {sun = new Sun(); //沒有sun才构造}return sun;}
}
可以看到我们一开始并没有造太阳,所以去掉了关键字final,只有在某线程第一次调用第9行的getInstance()方法时才会运行对太阳进行实例化的逻辑代码,之后再请求就直接返回此实例了。这样的好处是如无请求就不实例化,节省了内存空间;而坏处是第一次请求的时候速度较之前的饿汉初始化模式慢,因为要消耗CPU资源去临时造这个太阳(即使速度快到可以忽略不计)。这样的程序逻辑看似没问题,但其实在多线程模式下是有缺陷的。试想如果是并发请求的话,程序第10行的判空逻辑就会同时成立,这样就会多次实例化太阳,并且对sun进行多次赋值(覆盖)操作,这违背了单例的理念。我们再来改良一下,把请求方法加上synchronized(同步锁)让其同步,如此一来,某线程调用前必须获取同步锁,调用完后会释放锁给其他线程用,也就是给请求排队,一个接一个按顺序来
public class Sun {private static Sun sun = new Sun();public Sun() {} // private constructorpublic static synchronized Sun getInstance(){if (null == sun) {sun = new Sun(); //沒有sun才构造}return sun;}
}
我们将太阳类Sun中第9行的getInstance()改成了同步方法,如此可避免多线程陷阱。然而这样的做法是要付出一定代价的,试想,线程还没进入方法内部便不管三七二十一直接加锁排队,会造成线程阻塞,资源与时间被白白浪费。我们只是为了实例化一个单例对象而已,犯不上如此兴师动众,使用synchronized让所有请求排队等候。所以,要保证多线程并发下逻辑的正确性,同步锁一定要加得恰到好处,其位置是关键所在
public class Sun {private volatile static Sun sun = new Sun();public Sun() {} // private constructorpublic static Sun getInstance(){if (null == sun) {synchronized (Sun.class) {if(null == sun){sun = new Sun(); //沒有sun才构造,只有第一次才构造 保证线程安全}}}return sun;}
}
我们在太阳类Sun中第3行对sun变量的定义不再使用find关键字,这意味着它不再是常量,而是需要后续赋值的变量;而关键字volatile对静态变量的修饰则能保证变量值在各线程访问时的同步性、唯一性。需要特别注意的是,对于第9行的getInstance()方法,我们去掉了方法上的关键字synchronized,使大家都可以同时进入方法并对其进行开发。请仔细阅读每行代码的注释,有些人(线程)起早就是为了观看日出,那么这些人会通过第10行的判空逻辑进入观日台。而在第11行我们又加上了同步块以防止多个线程进入,这就类似于观日台是一个狭长的走廊,大家排队进入。随后在第12行我们又进行一次判空逻辑,这就意味着只有队伍中的第一个人造了太阳,有幸看到了日出的第一缕阳光,而后面的人则统统离开,直到第17行得到已经造好的太阳
随后发生的事情我们就可以预见了,太阳高高升起,实例化操作完毕,起晚的人们都无须再进入观日台,直接获取太阳实例就可以了,阳光普照大地,将温暖洒向人间。大家注意到没有,我们一共用了2个嵌套的判空逻辑,这就是懒加载模式的“双检锁”:外层放宽入口,保证线程并发的高效性;内层加锁同步,保证实例化的单次运行。如此里应外合,不仅达到了单例模式的效果,还完美地保证了构建过程的运行效率,一举两得。
2.4 大道至简
相比“懒汉模式”,其实在大多数情况下我们通常会更多地使用“饿汉模式”,原因在于这个单例迟早是要被实例化占用内存的,延迟懒加载的意义并不大,加锁解锁反而是一种资源浪费,同步更是会降低CPU的利用率,使用不当的话反而会带来不必要的风险。越简单的包容性越强,而越复杂的反而越容易出错。我们来看单例模式的类结构,如图2-3所示。单例模式的角色定义如下。
■ Singleton(单例):包含一个自己的类实例的属性,并把构造方法用private关键字隐藏起来,对外只提供getInstance()方法以获得这个单例对象。
除了“饿汉”与“懒汉”这2种单例模式,其实还有其他的实现方式。但万变不离其宗,它们统统都是由这2种模式发展、衍生而来的。我们都知道Spring框架中的IoC容器很好地帮我们托管了业务对象,如此我们就不必再亲自动手去实例化这些对象了,而在默认情况下我们使用的正是框架提供的“单例模式”。诚然,究其代码实现当然不止如此简单,但我们应该追本溯源,抓住其本质的部分,理解其核心的设计思想,再针对不同的应用场景做出相应的调整与变动,结合实践举一反三。
相关文章:
设计模式之单例设计模式
单例设计模式 2.1 孤独的太阳盘古开天,造日月星辰。2.2 饿汉造日2.3 懒汉的队伍2.4 大道至简 读《秒懂设计模式总结》 单例模式(Singleton)是一种非常简单且容易理解的设计模式。顾名思义,单例即单一的实例,确切地讲就是指在某个系统中只存在…...
Java自学到什么程度就可以去找工作了?
引言 Java作为一门广泛应用于软件开发领域的编程语言,对于初学者来说,了解到什么程度才能开始寻找实习和入职机会是一个常见的问题。 本文将从实习和入职这两个方面,分点详细介绍Java学习到什么程度才能够开始进入职场。并在文章末尾给大家安…...
三、Kafka生产者
目录 3.1 生产者消息发送流程3.1.1 发送原理 3.2 异步发送 API3.3 同步发送数据3.4 生产者分区3.4.1 kafka分区的好处3.4.2 生产者发送消息的分区策略3.4.3 自定义分区器 3.5 生产者如何提高吞吐量3.6 数据可靠性 3.1 生产者消息发送流程 3.1.1 发送原理 3.2 异步发送 API 3…...
【SA8295P 源码分析】19 - QNX Host NFS 文件系统配置
【SA8295P 源码分析】19 - QNX Host NFS 文件系统配置 一、NFS Server二、NFS Client三、NFS 相关的文件及目录四、将文件放入QNX 文件系统中五、编译下载验证系列文章汇总见:《【SA8295P 源码分析】00 - 系列文章链接汇总》 本文链接:《【SA8295P 源码分析】19 - QNX Host N…...
JRE、JDK、JVM及JIT之间有什么不同?_java基础知识总结
当涉及Java编程和执行时,以下术语具有不同的含义: 1.JRE (Java Runtime Environment) JRE是Java运行时环境的缩写。它是一个包含用于在计算机上运行Java应用程序所需的组件集合。JRE包括了以下几个主要部分: Java虚拟机(JVM):用…...
sqlite3数据库的实现
sqlite3代码实现数据库的插入、删除、修改、退出功能 #include <head.h> #include <sqlite3.h> #include <unistd.h> int do_insert(sqlite3 *db); int do_delete(sqlite3 *db); int do_update(sqlite3 *db);int main(int argc, const char *argv[]) {sqlit…...
c#设计模式-结构型模式 之 桥接模式
前言 桥接模式是一种设计模式,它将抽象与实现分离,使它们可以独立变化。这种模式涉及到一个接口作为桥梁,使实体类的功能独立于接口实现类。这两种类型的类可以结构化改变而互不影响。 桥接模式的主要目的是通过将实现和抽象分离,…...
【Vue-Router】导航守卫
前置守卫 main.ts import { createApp } from vue import App from ./App.vue import {router} from ./router // import 引入 import ElementPlus from element-plus import element-plus/dist/index.css const app createApp(App) app.use(router) // use 注入 ElementPlu…...
07无监督学习——降维
1.降维的概述 维数灾难(Curse of Dimensionality):通常是指在涉及到向量的计算的问题中,随着维数的增加,计算量呈指数倍增长的一种现象。 1.1什么是降维? 1.降维(Dimensionality Reduction)是将训练数据中的样本(实例)从高维空间转换到低维…...
系列七、IOC操作bean管理(xml自动装配)
一、概述 自动装配是根据指定规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入。 二、分类 xml自动装配分为按照属性名称自动装配(byName)和按照属性类型自动装配(byType)。 2.1…...
01- vdom 和模板编译源码
组件渲染的过程 template --> ast --> render --> vDom --> 真实的Dom --> 页面 Runtime-Compiler和Runtime-Only的区别 - 简书 编译步骤 模板编译是Vue中比较核心的一部分。关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步&am…...
C++入门知识点——解决C语言不足
😶🌫️Take your time ! 😶🌫️ 💥个人主页:🔥🔥🔥大魔王🔥🔥🔥 💥代码仓库:🔥🔥魔…...
探秘分布式大数据:融合专业洞见,燃起趣味火花,启迪玄幻思维
文章目录 一 数据导论二 大数据的诞生三 大数据概论3.1 大数据的5V特征3.2 大数据的工作核心 四 大数据软件生态4.1 数据存储软件4.2 数据计算软件4.3 数据传输软件 五 Apache Hadoop概述5.1 Apache Hadoop框架5.2 Hadoop的功能5.3 Hadoop的发展5.4 Hadoop发行版本 一 数据导论…...
什么是 SPI,和API有什么区别?
面试回答 Java 中区分 API 和 SPI,通俗的讲:API 和 SPI 都是相对的概念,他们的差别只在语义上,API 直接被应用开发人员使用,SPI 被框架扩展人员使用。 API Application Programming Interface 大多数情况下ÿ…...
python3 安装clickhouse_sqlalchemy(greenlet) 失败
环境信息: centos7操作系统,python3.8 执行pip3 install clickhouse_sqlalchemy或者pip3 install greenlet报以下报错: Command "/opt/python3.6.10-customized/bin/python3.6 -u -c "import setuptools, tokenize;file/tmp/pip-in…...
五款拿来就能用的炫酷表白代码
「作者主页」:士别三日wyx 「作者简介」:CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」:小白零基础《Python入门到精通》 五款炫酷表白代码 1、无限弹窗表白2、做我女朋友好吗,不同意就关机3、…...
Springboot 封装整活 Mybatis 动态查询条件SQL自动组装拼接
前言 ps:最近在参与3100保卫战,战况很激烈,刚刚打完仗,来更新一下之前写了一半的博客。 该篇针对日常写查询的时候,那些动态条件sql 做个简单的封装,自动生成(抛砖引玉,搞个小玩具&a…...
宝塔部署Java+Vue前后端分离项目经验总结
前言 之前部署服务器都是在Linux环境下自己一点一点安装软件,听说用宝塔傻瓜式部署更快,这次浅浅尝试了一把。 确实简单! 1、 买服务器 咋买服务器略,记得服务器装系统就装 Cent OS 7系列即可,我装的7.6。 2、创建…...
【公告】停止更新
CSDN 博客的限制太多了。阅读体验也非常差。后续将不再 CSDN 上更新。 逐步迁移到掘金和个人博客。 欢迎关注 掘金:0xforee 个人博客:0xforee’s blog...
AutoHotKey+VSCode开发扩展推荐
原来一直用的大众推荐的SciTeAHK版,最近发现VSCode更舒服一些,有几个必装的扩展推荐一下: AutoHotkey Plus 请注意不是AutoHotkey Plus Plus。如果在扩展商店里搜索会有两个,一个是Plus,一个是Plus Plus。我选择Pllus&…...
了解 JSON 格式
一、JSON 基础 JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的数据交换格式,JSON 的设计目的是使得数据的存储和交换变得简单。 JSON 易于人的阅读和书写,同时也易于机器的解析和生成。尽管 J…...
[RDMA] 高性能异步的消息传递和RPC :Accelio
1. Introduce Accelio是一个高性能异步的可靠消息传递和RPC库,能优化硬件加速。 RDMA和TCP / IP传输被实现,并且其他的传输也能被实现,如共享存储器可以利用这个高效和方便的API的优点。Accelio 是 Mellanox 公司的RDMA中间件,用…...
typescript报错:‘name‘ was also declared here
问题再现 用 Typescript 时, 遇到一个声明常量 name 的报错。代码如下: let name:string"zhangsan"; let num:number1001;执行编译时报错: 原因 在默认状态下,typescript 将 DOM typings 作为全局的运行环境&#…...
第十章:联邦学习视觉案例
代码 传送门...
c语言——输出一个整数的所有因数
//输出一个整数的所有因数 #include<stdio.h> #include<stdlib.h> int main() {int number,i;printf("输入整数:");scanf("%d",&number);printf(" %d 的因数有: ",number);for(i1;i<number;i){if(numb…...
mqtt学习记录
目录 1 匿名登录2 ⽤户名密码登录,配置接收的主题mosquitto 配置文件修改添加⽤户信息添加topic和⽤户的关系登录演示 3 遗嘱机制 1 匿名登录 ⾸先打开三个终端, 启动代理服务:mosquitto -v -v 详细模式 打印调试信息 默认占⽤:…...
爬虫逆向实战(十八)--某得科技登录
一、数据接口分析 主页地址:某得科技 1、抓包 通过抓包可以发现数据接口是AjaxLogin 2、判断是否有加密参数 请求参数是否加密? 查看“载荷”模块可以发现有一个password加密参数和一个__RequestVerificationToken 请求头是否加密? 无…...
Java-数组
什么是数组 数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。 在java中, 数组中存放的元素其类型相同数组的空间是连在一起的每个空间有自己的编号,起始位置的编号为0,即数组的下标。 数组的创建及初始化 数…...
Dart 入门Hello world
1、下载Dart sdk IntelliJ & Android Studio | Dart 2、安装Dart 插件 3、安装后重启IDEA,创建Dart项目 4、创建dart文件 5、编写函数: void main() {print("Hello world"); } 6、运行: 官网学习:Dart 语言开发文…...
HTML是什么?
HTML是什么? 超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。 您可以使用 HTML 来建立自己的 WEB 站点,HTML 运行在浏览器上,由浏览器…...
室内设计师的网站/sem投放是什么意思
反向代理简介----服务器的代理代理服务器是使用非常普遍的一种将局域网主机联入互联网的一种方式,使用代理上网可以节约紧缺的IP地址资源,而且可以阻断外部主机对内部主机的访问,使 内部网主机免受外部网主机的***。但是,如果想让…...
oss做静态网站/宁波谷歌优化
光学三维测量是什么? 光学三维测量时通过运动适当的光学和电子仪器非接触地获取被测物体外部形貌的方法和技术。光学三维测量是指对物体空间外形和结构进行扫描,以获得物体表面点的三维空间坐标的一项技术。 有什么用? 光学测量在工业自动检测…...
网站首页做的好看/天津百度快速排名优化
原创不易,转载请注明出处 前言 到目前为止,RocketMQ源码解析博文已经发表了20篇,初步打算是从两个维度来解析RocketMQ的源码,分别是整体运行流程的源码解析,某些核心组件源码深度剖析。现在已经初步完成了RocketMQ核心…...
日本网站欣赏/宁海关键词优化怎么优化
一、需求:有这么一张表前四个属性当作联合主键需要把该表所有的行在前端以表格形式显示出来,要求activityId相同时合并成一行,activityCode相同时,合并一行,activityVersion相同时也合并一行。类似这种:二、…...
手机社区网站模板/手机推广app
Linux程序包管理器(rpm,yum,源码安装)一.RPM程序包管理器debian的软件包管理器为deb,软件包后缀为.dpt。而红帽的程序包管理器为rpm,包后缀为.rpmRPM是redhat发行版专门用来管理Linux软件包的,最早由Red Hat开发出来,后…...
电子商务网站建设也管理/近几天发生的新闻大事
asp.net编写登录注册代码 win7 asp.netms sql 2005vs2010(开发平台)数据库名userinfousername varchar(50) userpassword varchar(50) 连接数据库,写在类db.cs里面,其代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Web; usi…...