单例模式有几种写法?请谈谈你的理解?

为什么有单例模式?

单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。


实现原理是什么?

构造方法是private+static方法+if语句判断
注意:不同的实现方式它的实现原理肯定是有所区别的,综合来看!!


实现方式有哪些?

懒汉式、双重锁、饿汉式、静态内部类、枚举


懒汉式

  • 好处:启动速度快、节省资源,一直到实例被第一次访问,才需要初始化单例、避免空间浪费;
  • 缺点:线程不安全,if语句存在竞态条件

单例类

package com.example;

/**
 * @BelongsProject: BigK
 * @BelongsPackage: com.example
 * @Author: dengLiMei
 * @CreateTime: 2023-06-28  10:04
 * @Description: 单例模式
 * @Version: 1.0
 */
public class Singleton {
    //提供一个全局变量让全局访问
    private static Singleton instance;

    //私有构造方法,堵死外界利用new创建此类实例的可能
    private Singleton() {
    }

    //获得实例的唯一全局访问点
    public static Singleton GetInstance() {
        //当多线程来临的时候判断是否为null,此时instance就是临界资源,会实例化多个
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

客户端

//反射破坏封装性
Singleton instance1 = Singleton.GetInstance();

// 使用反射获取私有构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);

// 通过反射创建第二个实例
Singleton instance2 = constructor.newInstance();

System.out.println(instance1); // 输出第一个实例的内存地址
System.out.println(instance2); // 输出第二个实例的内存地址

这里我是通过反射的方式去获取对象,然后对获取到的对象进行判断,运行代码之后我们会发现:
在这里插入图片描述
两个对象的内存地址并不相同,违背了单一性,那我们如何解决这个问题呢?可能屏幕前有些小伙伴想到了加锁的方式去做,没错,我们用大家比较常见的synchronized实现看看吧。


懒汉式变种-synchronized

  • 好处:线程安全
  • 缺点:并发性能差,synchronized加锁,不管有没有对象都加锁
    单例类
package com.example;

/**
 * @BelongsProject: BigK
 * @BelongsPackage: com.example
 * @Author: dengLiMei
 * @CreateTime: 2023-06-28  10:14
 * @Description: 懒汉单例:在第一次被引用时,才会将自己实例化
 * @Version: 1.0
 */
public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
        System.out.println("创建一次");
    }


    public static LazySingleton GetInstance() {
        //方法一:加锁-把判断的这部分逻辑上锁
        //好处:线程安全
        //缺点:并发性能差,synchronized加锁,不管有没有对象都加锁
        //解决方案:双重锁
        synchronized ("") {
            if (instance == null) {
                instance = new LazySingleton();
            }
        }
        return instance;
    }

    //方法二:同步代码段
    public static synchronized LazySingleton getSingleton() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

客户端

  //懒汉模式:加锁保证线程安全
Runnable r3 = () -> {

DoubleLockSingleton s1 = DoubleLockSingleton.GetInstance();
DoubleLockSingleton s2 = DoubleLockSingleton.GetInstance();

if (s1 == s2) {
	System.out.println("两个对象是相同的实例");
}
};

Thread t1 = new Thread(r3);
Thread t2 = new Thread(r3);

t1.start();
t2.start();

在这里插入图片描述
通过运行结果我们会发现两个线程获取到的对象是同一个,实现了单例。
但是大家可以思考一下这样会不会存在什么问题呢?线程因为每次访问 getInstance() 方法时都需要获取锁,即使实例已经被创建,会在高并发环境下其实是比较影响性能的。并且会导致每次调用 getInstance() 方法都需要获取锁,而不是在需要时才创建实例。那我们可不可以当单例对象没有被创建的时候才去加锁呢?双重锁可以做到

懒汉式变种-双重锁

  • 好处:实现线程安全地创建实例,而又不会对性能造成太大影响。
  • 缺点:无效等待,同步效率地,锁占用资源(反射会破坏单一性)
    单例类
package com.example;

/**
 * @BelongsProject: BigK
 * @BelongsPackage: com.example
 * @Description: 懒汉单例——双重锁
 * @Version: 1.0
 */
public class DoubleLockSingleton {
    //volatile:禁止指令重排序(防止部分初始化)
    private static volatile DoubleLockSingleton instance;

    private DoubleLockSingleton() {

        System.out.println("实例化了一次");
    }

    //原理:双重if,延迟实例化,避免每次进行同步的性能开销
    public static DoubleLockSingleton GetInstance() {
        //第一层判断:先判断实例是否存在,不存在再加锁处理
        if (instance == null) {
            synchronized ("") {
                //第二层判断
                if (instance == null) {
                    instance = new DoubleLockSingleton();
                }
            }
        }
        return instance;
    }
}

客户端

DoubleLockSingleton instance1 = DoubleLockSingleton.GetInstance();

// 使用反射获取私有构造函数
Constructor<DoubleLockSingleton> constructor = DoubleLockSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);

// 通过反射创建第二个实例
DoubleLockSingleton instance2 = constructor.newInstance();

System.out.println(instance1); // 输出第一个实例的内存地址
System.out.println(instance2); // 输出第二个实例的内存地址

这里我们依旧使用反射去获取单例对象。我们运行看看效果:
在这里插入图片描述
发现构造方法被调用了两地,并且获取到的两个对象的地址也不同,依旧是破坏了单例性。
双重锁实现方式是在第一次创建实例的时候同步,以后就不需要同步了。反射的使用让我们的单例类又不攻自破,没关系,咱们还有其他方式——饿汉式


饿汉式

  • 优点:类加载阶段创建,保证了线程安全
  • 缺点:可能存在没有被使用的可能,造成资源浪费

单例类

package com.example;

/**
 * 饿汉模式:类加载时初始化单例,以后访问时直接返回即可
 */
public class HungrySingleton {
    //类加载阶段就实例化
    private static final HungrySingleton singleton = new HungrySingleton();

    private HungrySingleton() {
    }

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

客户端

//获取单例对象
HungrySingleton singleton = HungrySingleton.getInstance();

// 使用反射获取单例对象
try {
	Class<?> singletonClass = Class.forName("com.example.HungrySingleton");

	// 获取私有构造函数
	Constructor<?> constructor = singletonClass.getDeclaredConstructor();
	constructor.setAccessible(true);

	// 通过反射实例化对象
	HungrySingleton singletonReflection = (HungrySingleton) constructor.newInstance();

	// 验证是否为同一对象
	System.out.println(singleton == singletonReflection);  // 输出 true
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
	e.printStackTrace();
}

使用反射获取单例对象,我们看下输出结果:
在这里插入图片描述
在整个应用程序的生命周期中,无论是否会用到该单例实例,都会在类加载时创建实例,可能会导致资源的浪费。饿汉模式无法实现延迟加载,即在需要时才创建实例。这可能会导致在应用程序启动时就创建了大量的实例,占用内存。
基于这些原因,尽管饿汉模式是一种简单且线程安全的单例模式实现方式,但在资源利用、延迟加载和异常处理等方面存在一些问题。所以我们在实际使用过程中需要根据具体场景选择合适的单例模式实现方式


静态内部类

好处:

  • 懒加载:静态内部类的方式能够实现懒加载,即在需要时才会加载内部类,从而创建单例对象。这样可以避免在类加载时就创建单例对象,节省了资源。
  • 线程安全:静态内部类的方式利用了类加载机制和静态变量的特性,能够保证在多线程环境下也能够保持单例的唯一性,而且不需要使用同步关键字。
  • 延迟加载:由于静态内部类的加载是在需要时才进行的,因此能够实现延迟加载,即在第一次使用时才会创建单例对象。

缺点:静态内部类的方式需要额外的类加载和内存开销,因为它需要创建一个内部类对象,而内部类对象的创建需要额外的内存开销。

单例类

package com.example;

/**
 * 静态内部类
 * 
 */
public class StaticInnerSingleton {
    //静态内部类
    private static class SingletonHolder {
        private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
    }

    private StaticInnerSingleton (){}


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

}

客户端

//获取单例对象
StaticInnerSingleton singleton = StaticInnerSingleton.getInstance();

// 使用反射获取单例对象
try {
	Class<?> singletonClass = Class.forName("com.example.StaticInnerSingleton");
	
	// 获取私有构造函数
	Constructor<?> constructor = singletonClass.getDeclaredConstructor();
	constructor.setAccessible(true);
	
	// 通过反射实例化对象
	StaticInnerSingleton singletonReflection = (StaticInnerSingleton) constructor.newInstance();
	
	// 验证是否为同一对象
	System.out.println(singleton == singletonReflection);  // 输出 true
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
	e.printStackTrace();
}

我们来看看运行结果:
在这里插入图片描述
获取的两个对象的地址是不相同的,实现了单例。
它利用了类加载的特性和静态内部类的懒加载特性,解决了饿汉模式的资源浪费和懒汉模式的线程安全问题。具体实现方式是在外部类中定义一个私有的静态内部类,内部类中创建单例实例,并且利用类加载的特性保证了实例的唯一性。同时,由于静态内部类是在需要的时候才加载,因此实现了延迟加载的效果。也是比较推荐的一种方式


枚举

优点:线程安全、防止反序列化重新创建新的对象
单例类

package com.example;

/**
 * 枚举方式
 */
public enum EnumSingleton {
    INSTANCE;
}

客户端

 // 获取单例对象
EnumSingleton singleton1 = EnumSingleton.INSTANCE;
EnumSingleton singleton2 = EnumSingleton.INSTANCE;

// 验证是否为同一对象
System.out.println(singleton1 == singleton2);  // 输出 true

我们来让控制台打印输出看看结果:
在这里插入图片描述
在Java中,枚举类型是线程安全的,并且保证在任何情况下都是单例的。因此,使用枚举实现单例模式是一种推荐的方式。具体实现方式是定义一个包含单个枚举常量的枚举类型,这个枚举常量就是单例实例。由于枚举类型在Java中是天然的单例,因此不需要担心线程安全和反射攻击等问题。


使用场景有哪些?

Windows的Task Manager(任务管理器)、回收站


使用时如何选择?

在这里插入图片描述
在实际业务场景中,可以根据具体需求选择适合的单例模式。如果需要在应用启动时创建对象,且对性能要求较高,可以选择饿汉式或双重校验锁;如果需要延迟加载对象,可以选择静态内部类或枚举单例模式;如果对线程安全要求较高,可以选择双重校验锁或静态内部类单例模式



如果有想要交流的内容欢迎在评论区进行留言,如果这篇文档受到了您的喜欢那就留下你点赞+收藏+评论脚印支持一下博主~

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

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

相关文章

coreldraw怎么添加箭头?

使用coreldraw的时候知道箭头在哪里添加吗&#xff1f;下面小编就给大家带来coreldraw箭头添加教程&#xff0c;有需要的小伙伴不要错过哦。 coreldraw添加箭头方法 1、首先选择桌面Coreldraw格式图片。 2、然后点击文件夹按钮打开文件。 3、最后点击上方工具横线&#xff0c…

免费的ChatGPT网站 ( 7个 )

ChatGPT的核心功能是基于用户在输入时的语言或文本生成相应的回复或继续内容。此外&#xff0c;它还能够完成多种任务&#xff0c;如撰写邮件、视频脚本、文案、翻译、代码编写以及撰写论文等。 博主归纳总结了7个国内非常好用&#xff0c;而且免费的chatGPT网站&#xff0c;AI…

qt学习:http+访问百度智能云api实现车牌识别

目录 登录到百度智能云&#xff0c;找到文字识别 完成操作指引 开通 查看车牌识别的api文档 ​编辑​编辑 查看自己应用的api key 查看回应的数据格式 编程步骤 ui界面编辑 添加模块&#xff0c;头文件和定义变量 新建两个类&#xff0c;一个图像Image类&#xff0c…

使用Docker部署WBO白板并结合内网穿透实现远程访问WBO白板

本文主要是如何使用Docker部署WBO白板并实现公网地址远程访问的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&am…

leetcode刷题(剑指offer)54.螺旋矩阵

54.螺旋矩阵 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#xff1a;ma…

编译和链接哪个才是最“猴急”的呢???

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能…

Windows Server 2003 DNS服务器搭建

系列文章目录 目录 系列文章目录 文章目录 前言 一、DNS服务器是什么&#xff1f; 二、配置服务器 1.实验环境搭建 2.服务器搭建 3)安装Web服务器和DNS服务器 4)查看安装是否成功 5)这里直接配置DNS服务器了,Web服务器如何配置我已经发布过了 文章目录 Windows Serve…

从困惑到精通:私域运营领域10大问题全解析!

一、到底什么是“私域” 在探讨私域流量的实际操作之前&#xff0c;品牌需要对“私域”有一个清晰的认识。那么&#xff0c;究竟什么是“私域”呢&#xff1f; 有观点认为&#xff0c;私域是品牌方掌握的用户群体&#xff0c;这些用户可以在品牌的运营、沉淀和变现中发挥重要…

【第二十二课】最短路:dijkstra算法 ( acwing849 / acwing850 / c++ 代码)

目录 dijkstra算法求最短距离步骤 朴素的dijkstra算法---acwing-849 代码如下 代码思路 堆优化版的dijkstra算法---acwing-850 代码如下 关于最短路问题分有好几种类型 &#xff1a; 单源就是指&#xff1a;只求从一个顶点到其他各顶点 多源是指&#xff1a;要求每个顶…

基于springboot+vue的旅游管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究现状…

uniapp顶部导航栏高度适配

为了实现好看又实用的顶部导航栏&#xff0c;不得不自己定义导航栏样式。而自己定义的导航栏高度会因为手机的型号不同所展示的效果也就不同&#xff0c;所以只能通过适配高度来达到预期的效果 1.需要在page.json文件中对需要自定义导航栏文件进行配置 "navigationStyle…

Leetcode第382场周赛

Leetcode第382场周赛 本人水平有限&#xff0c;只做前三道。 一、按键变更的次数 给你一个下标从 0 开始的字符串 s &#xff0c;该字符串由用户输入。按键变更的定义是&#xff1a;使用与上次使用的按键不同的键。例如 s “ab” 表示按键变更一次&#xff0c;而 s “bBBb”…

机器学习:梯度下降法(Python)

LinearRegression_GD.py import numpy as np import matplotlib.pyplot as pltclass LinearRegression_GradDesc:"""线性回归&#xff0c;梯度下降法求解模型系数1、数据的预处理&#xff1a;是否训练偏置项fit_intercept&#xff08;默认True&#xff09;&…

【数据分享】1929-2023年全球站点的逐月最高气温数据(Shp\Excel\无需转发)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01; 之前我们分享过1929-2023年全球气象站…

C# Onnx yolov8 仪表指针检测

目录 效果 模型信息 项目 代码 训练数据 下载 C# Onnx yolov8 仪表指针检测 效果 模型信息 Model Properties ------------------------- date&#xff1a;2024-01-31T11:19:38.828556 author&#xff1a;Ultralytics task&#xff1a;detect license&#xff1a;AGPL-…

wordpress怎么做产品展示站?推荐使用MOK主题和ent主题

大多数WordPress站点都是个人博客网站&#xff0c;主要以文章性质的图文为主。不过部分站长想要用WordPress搭建一个产品展示站&#xff0c;应该怎么做呢&#xff1f; 其实&#xff0c;WordPress可以用来建立各种各样的博客网站&#xff0c;包括个人博客、企业网站、商城、影视…

小白级教程,10秒开服《幻兽帕鲁》

在帕鲁的世界&#xff0c;你可以选择与神奇的生物「帕鲁」一同享受悠闲的生活&#xff0c;也可以投身于与偷猎者进行生死搏斗的冒险。帕鲁可以进行战斗、繁殖、协助你做农活&#xff0c;也可以为你在工厂工作。你也可以将它们进行售卖&#xff0c;或肢解后食用。 前言 马上过年…

带大家详细了解msvcr120.dll丢失的原因,msvcr120.dll丢失怎样修复的方法

在使用电脑和运行应用程序时&#xff0c;我们经常会遇到与动态链接库&#xff08;Dynamic Link Library, DLL&#xff09;文件相关的错误。其中之一是 "msvcr120.dll 丢失" 的错误提示。今天我们就来详细的了解一下msvcr120.dll这个文件和分享msvcr120.dll丢失怎样修…

从宏观上对人工智能(AI)的一些理解

1.人工智能概述 68年前&#xff0c;约翰麦卡锡在“达特茅斯会议”正式提出人工智能概念。直到2023年&#xff0c;ChatGPT掀起全球AI大模型浪潮&#xff0c;英伟达市值一年飙涨2.4倍&#xff0c;真正意义上的“人工智能元年”到来了。 提到人工智能&#x…

事件驱动架构:使用Flask实现MinIO事件通知Webhooks

MinIO的事件通知可能一开始看起来并不激动人心&#xff0c;但一旦掌握了它们的力量&#xff0c;它们就能照亮您存储桶内的动态。事件通知是一个全面、高效的对象存储系统中的关键组件。Webhooks是我个人最喜欢的工具&#xff0c;用于与MinIO集成。它们在事件的世界中就像一把瑞…