设计模式之单例设计模式

单例设计模式

  • 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 constructor

    public static Sun getInstance(){
        return sun;
    }
}

太阳单例类的雏形已经完成了,对外部来说只要调用Sun.getInstance()就可以得到太阳对象了,并且不管谁得到,或是得到几次,得到的都是同一个太阳实例,这样就确保了整个太阳系中恒星太阳的唯一合法性,他人无法伪造。当然,读者还可以添加其他功能方法,如发光和发热等,此处就不再赘述了。

2.3 懒汉的队伍

至此,我们已经学会了单例模式的“饿汉模式”,让太阳一开始就准备就绪,随时供应免费日光。然而,如果始终没人获取日光,那岂不是白造了太阳,一块内存区域被白白地浪费了?这正类似于商家货品滞销的情况,货架上堆放着商品却没人买,白白浪费空间。因此,商家为了降低风险,规定有些商品必须提前预订,这就是“懒汉模式”(lazy initialization)。沿着这个思路,我们继续对太阳类进行改造

public class Sun {
    private static Sun sun = new Sun();

    public Sun() {} // private constructor

    public 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 constructor

    public 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 constructor

    public 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容器很好地帮我们托管了业务对象,如此我们就不必再亲自动手去实例化这些对象了,而在默认情况下我们使用的正是框架提供的“单例模式”。诚然,究其代码实现当然不止如此简单,但我们应该追本溯源,抓住其本质的部分,理解其核心的设计思想,再针对不同的应用场景做出相应的调整与变动,结合实践举一反三。

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

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

相关文章

centos 7.9 部署django项目

1、部署框架 主要组件:nginx、uwsgi、django项目 访问页面流程:nginx---》uwsgi---》django---》uwsgi---》nginx 2、部署过程 操作系统:centos 7.9 配置信息:4核4G 50G 内网 eip :10.241.103.216 部署过程&…

pycharm调整最大堆发挥最大

python程序运行时,怎么提高效率,设置pycharm最大堆过程如下; 一、进入设置pycharm最大堆; 二、进入设置pycharm最大堆; 如果8g设置为6g左右,占75%左右最佳

数学建模之“TOPSIS数学模型”原理和代码详解

一、简介 TOPSIS(Technique for Order Preference by Similarity to Ideal Solution)是一种多准则决策分析方法,用于解决多个候选方案之间的排序和选择问题。它基于一种数学模型,通过比较每个候选方案与理想解和负理想解之间的相…

小程序CSS button按钮自定义高度之后不居中

问题&#xff1a; 按钮设置高度后不居中 <view><button class"btn1" size"">Save</button> </view> page {font-size: 30rpx; }.btn1 {margin-top: 100rpx;height: 190rpx;background: linear-gradient(90deg, #FF8A06, #FF571…

c语言每日一练(8)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;暑假时三天之内必有一更&#xff0c;到了开学之后&#xff0c;将看学业情…

微信小程序:模板使用

目录 模板的优点&#xff1a; 一、静态模板创建 二、静态模板使用 1.*.wxml引入模板 2.模板使用 3.*.wxss引入模板的样式 三、动态模板创建 四、动态模板使用 1.*.wxml引入模板 2.模板使用 3.*.js定义动态数据 五、结果展示 总结 模板的优点&#xff1a; 有利于保持网…

NVIDIA Omniverse与GPT-4结合生成3D内容

全球各行业对 3D 世界和虚拟环境的需求呈指数级增长。3D 工作流程是工业数字化的核心&#xff0c;开发实时模拟来测试和验证自动驾驶车辆和机器人&#xff0c;操作数字孪生来优化工业制造&#xff0c;并为科学发现铺平新的道路。 如今&#xff0c;3D 设计和世界构建仍然是高度…

HDFS原理剖析

一、概述 HDFS是Hadoop的分布式文件系统&#xff08;Hadoop Distributed File System&#xff09;&#xff0c;实现大规模数据可靠的分布式读写。HDFS针对的使用场景是数据读写具有“一次写&#xff0c;多次读”的特征&#xff0c;而数据“写”操作是顺序写&#xff0c;也就是…

免费开源服务器资源监控系统grafana+prometheus+node_exporter

有项目做测试的时候需要查询服务器资源利用情况&#xff0c;自己又没写相应的模块&#xff0c;此时就需要一套好用的资源监控系统&#xff0c;&#xff0c;咨询了运维人员给推荐了一套&#xff0c;装完后真的很好用。 就是grafanaprometheusnode_exporter&#xff08;linux&am…

DAY3,ARM(LED点灯实验)

1.汇编实现开发板三盏灯点亮熄灭&#xff1b; .text .global _start _start: /**********LED123点灯**************/RCC_INIT:1使能PE10 PF10 PE8RCC..寄存器,E[4]1 F[5]1 0x50000a28ldr r0,0x50000a28ldr r1,[r0]orr r1,r1,#(0x3 << 4)str r1,[r0]LED1_INET:2初始化LED…

Vue3实现图片懒加载及自定义懒加载指令

Vue3实现图片懒加载及自定义懒加载指令 前言1.使用vue3-lazyload插件2.自定义v-lazy懒加载指令2.1 使用VueUse2.2 使用IntersectionObserver 前言 图片懒加载是一种常见性能优化的方式&#xff0c;它只去加载可视区域图片&#xff0c;而不是在网页加载完毕后就立即加载所有图片…

安防监控视频云存储平台EasyNVR通道频繁离线的原因排查与解决

安防视频监控汇聚EasyNVR视频集中存储平台&#xff0c;是基于RTSP/Onvif协议的安防视频平台&#xff0c;可支持将接入的视频流进行全平台、全终端分发&#xff0c;分发的视频流包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等格式。为了满足用户的集成与二次开发需求&#xf…

H3C交换机如何配置本地端口镜像并在PC上使用Wireshake抓包

环境: H3C S6520-26Q-SI version 7.1.070, Release 6326 Win 10 专业版 Wireshake Version 4.0.3 问题描述: H3C交换机如何配置本地端口镜像并在PC上使用Wireshake抓包 解决方案: 配置交换机本地端口镜像 1.进入系统视图,并创建本地镜像组1 <H3C>system-vie…

酷开系统 | 酷开科技大数据,更好的与目标消费人群建立联系

众所周知&#xff0c;OTT的一大优势在于强曝光&#xff0c;能够给消费者带来强烈的视觉冲击&#xff0c;强化品牌认知。但是&#xff0c;要想达到提升品牌认知&#xff0c;首先要保证OTT的流量规模&#xff0c;实现对目标人群的有效覆盖。得年轻消费者得“天下”&#xff0c;年…

mac录屏工具,录屏没有声音的解决办法

mac录屏工具&#xff0c;录屏没有声音的解决办法 在使用macbook录制屏幕时&#xff0c;发现自带的录屏工具QuickTime Player没有声音&#xff0c;于是尝试了多款录屏工具&#xff0c;对其做一些经验总结&#xff08;省流&#xff1a;APP Store直接可以免费下载使用Omi录屏专家…

docker tomcat时间少8小时问题解决

docker容器与系统时间一致并且正确&#xff0c;但是java程序在运行中通过log日志发现发了8小时 解决方法 修改docker容器中tomcat/bin/catalina.sh文件&#xff0c;添加一下内容 JAVA_OPTS"$JAVA_OPTS -Dfile.encodingUTF8 -Duser.timezoneGMT08" 附 操作命令 一…

PHP实现轻量级WEB服务器接收HTTP提交的RFID刷卡信息并回应驱动读卡器显示播报语音

本示例使用的读卡器&#xff1a;RFID网络WIFI无线TCP/UDP/HTTP可编程二次开发读卡器POE供电语音-淘宝网 (taobao.com) <?php mb_http_output(utf-8); $port88; $socket socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $bool socket_bind($socket, "0.0.0.0",…

每天一道leetcode:1926. 迷宫中离入口最近的出口(图论中等广度优先遍历)

今日份题目&#xff1a; 给你一个 m x n 的迷宫矩阵 maze &#xff08;下标从 0 开始&#xff09;&#xff0c;矩阵中有空格子&#xff08;用 . 表示&#xff09;和墙&#xff08;用 表示&#xff09;。同时给你迷宫的入口 entrance &#xff0c;用 entrance [entrancerow, …

用dcker极简打包java.jar镜像并启动

用dcker极简打包java.jar镜像并启动 一、本地打包好jar包 二、新建文件夹&#xff0c;将步骤1中的jar包拷贝到文件夹下 三、同目录下新建Dockerfile ## 基础镜像&#xff0c;这里用的是openjdk:8 FROM openjdk:8## 将步骤一打包好的jar包 拷贝到镜像的 跟目录下[目录可以自定义…

Java 单例模式简单介绍

何为单例模式 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法。 实现思路 如果我们要让类在一个虚拟机中只能产生一个对象&#xff0c;我们首先必…