JavaEE 初阶篇-深入了解单例模式(经典单例模式:饿汉模式、懒汉模式)

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 单例模式的概述

        2.0 单例模式 - 饿汉式单例

        2.1 关于饿汉式单例的线程安全问题

        3.0 单例模式 - 懒汉式单例

        3.1 关于懒汉式单例的线程安全问题

        3.1.1 加锁 synchronized 方法

        3.1.2 双重检查锁定


        1.0 单例模式的概述

        单例模式是一种常见的设计模式,其核心思想是确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。单例模式通常用于需要全局访问点且只需要一个实例的情况。

单例特点包括:

        1)一个类只有一个实例对象。

        2)提供全局访问点,允许其对象访问这个实例。

        3)通过静态方法或静态变量实现。

常见的单例模式实现方法包括:

        1)饿汉式单例:在类加载时即创建实例。

        2)懒汉式单例:在第一次使用时创建实例。

使用单例模式的好处:

        使用单例模式可以避免多次实例化对象,节省内存资源,并且提供一个统一的访问点,方便管理和维护。

        2.0 单例模式 - 饿汉式单例

        “饿汉”顾名思义,有迫不及待的意思,看到食物就迫切想吃。因此,在类加载的时候,需要立即创建实例对象。为了保证只有一个实例对象,对外的静态对象实例需要封装起来,且对外的构造器也需要封装起来,对外有一个获取实例对象的静态方法即可。

代码如下:

public class Singleton {// 在类加载时即创建实例对象private static Singleton instance = new Singleton();// 将构造器设为私有,外部无法直接实例化private Singleton() {}// 提供一个公共的静态方法来获取实例对象public static Singleton getInstance() {return instance;}// 其他方法和属性
}

        通过这种方式,可以保证在程序运行过程中始终只有一个实例对象存在,且外部通过静态方法 getInstance() 来获取实例对象,确保了单例模式的实现。这种方式适合在程序初始化时就需要创建实例对象的情况下使用。

        2.1 关于饿汉式单例的线程安全问题

        在“饿汉式”单例模式中,由于在类加载时就创建实例对象,并且在静态变量中直接初始化实例,因此在多线程环境下是线程安全的。

        类加载过程是由类加载器负责,保证了在多线程环境下只会加载一次类,因此也只会创建一个实例对象。

        因此,如果使用“饿汉式”单例模式,在多线程环境下不需要额外的同步措施,可以保证线程安全。每个线程在访问该单例类的时候都会得到同一个实例对象,不会出现多个实例对象被创建的情况。

        3.0 单例模式 - 懒汉式单例

        当需要获取第一个实例对象的时候,才会去创建实例对象。

        为了保证该类只有一个实例对象,需要对外的构造方法封装,在还没获取实例对象的时候,将静态变量赋值为 null 。再提供一个静态方法来获取实例对象,当获取实例对象的时候,先会判断还对象的变量是否为 null ,如果为 null 时,这时候需要立即创建对象,然后返回该对象;如果实例对象的变量不为 null 时,直接方法该实例对象。

代码如下:

public class SingletonLazy {private static SingletonLazy singleton = null;//私有的构造方法private SingletonLazy(){}//获取实例对象的方法,变量不为 null 的时候直接返回该实例对象public static SingletonLazy getInstance(){if (singleton == null){singleton = new SingletonLazy();}return singleton;}//其他方法和属性}

        3.1 关于懒汉式单例的线程安全问题

        懒汉式单例模式在多线程环境下存在安全问题,需要通过加锁等方式来解决。

        在懒汉单例模式中,实例对象在第一次被调用时才会被创建,如果这时候,多个线程同时调用 getInstance() 方法,可能会导致多个实例对象被创建,破环了单例的特性。

解决方法:可以通过加锁方式来确保线程安全

        3.1.1 加锁 synchronized 方法

        在 getInstance() 方法上加上 synchronized 关键字,确保在多线程环境下只有一个线程可以进入该方法,保证只创建一个实例对象。但是这种方式会影响性能,因为每次调用 getInstance() 都需要获取锁。

代码如下:

public class SingletonLazy {private static SingletonLazy singleton = null;//私有的构造方法private SingletonLazy(){}//获取实例对象的方法,变量不为 null 的时候直接返回该实例对象public static synchronized SingletonLazy getInstance(){if (singleton == null){singleton = new SingletonLazy();}return singleton;}//其他方法public void fun(){System.out.println("懒汉单例模式");}
}

        在静态方法 getInstance() 方法上加上了 synchronized 修饰。每当多线程抢占获取锁的时候,没有获取锁的线程需要阻塞等待。这就保证了线程安全问题。

        3.1.2 双重检查锁定

        从代码执行效率这方面来看,每一个获取实例对象的时候,都需要阻塞等待,且只有创建对象的时候才需要真正的用到锁这个作用,其余大部分不需要来创建实例对象,只需要获取实例对象。

        在 getInstance() 方法中进行双重检查,先判断实例是否已经被创建,如果没有再进行加锁创建实例。这种方式可以减少加锁的次数,提高性能。

优化代码如下:

public class SingletonLazy {private static volatile SingletonLazy singleton = null;//私有的构造方法private SingletonLazy(){}//获取实例对象的方法,变量不为 null 的时候直接返回该实例对象public static SingletonLazy getInstance(){if (singleton == null){synchronized (SingletonLazy.class){if (singleton == null){singleton = new SingletonLazy();}}}return singleton;}//其他方法public void fun(){System.out.println("懒汉单例模式");}
}

        第一个 if 判断是否要加锁,实例化之后,线程自然安全,就无需加锁了,实例化之前, new 之前,就应该加锁。

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

        通过双重 if 避免了不可重复读带来的负面影响,避免了重复创建对象。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】排序算法 --快速排序与归并排序

目录 颜色分类(数组分三块思想)快速排序归并排序 颜色分类(数组分三块思想) 给定⼀个包含红⾊、⽩⾊和蓝⾊、共 n 个元素的数组 nums ,原地对它们进⾏排序,使得相同颜⾊ 的元素相邻,并按照红⾊、…

【Servlet】服务器内部转发以及客户端重定向

文章目录 一、服务器内部转发:request.getRequestDispatcher("...").forward(request, response);二、客户端重定向:response.sendRedirect("");三、服务器内部转发代码示例四、客户端重定向代码示例 一、服务器内部转发&#xff1a…

卡尔曼滤波笔记

资料:https://www.zhihu.com/question/47559783/answer/2988744371 https://www.zhihu.com/question/47559783 https://blog.csdn.net/seek97/article/details/120012667 一、基本思想 在对一个状态值进行估计的时候,如果想测量值更准,很自然…

【Vue3源码学习】— CH2.7 Computed: Vue 3 计算属性深入解析

Computed: Vue 3 计算属性深入解析 1.计算属性的基本用法2. ComputedRefImpl 类深入解析JavaScript 中的 getter 函数 3. 计算属性的创建:computed 方法解析3.1 源码解析3.2 使用示例 4. 计算属性的工作原理5. 手动实现简化的计算属性6. 结语 在 Vue 3 的响应式系统…

【Go】二十、反射

文章目录 1、反射2、对基本数据类型反射3、对结构体进行反射4、获取变量的类别5、通过反射修改基本类型变量的值6、通过反射操作结构体的属性和方法 1、反射 //核心包 import ("reflect")通过反射: 可以在运行时动态获取变量的类型、获取结构体的信息&a…

mac mini m1芯片 Xcode 15.3 各种报错的问题

错误一: /Users/mac/Desktop/Test_project/mobile-ios/Test/Test-Bridging-Header.h:4:9 failed to emit precompiled header /Users/mac/Library/Developer/Xcode/DerivedData/App-apvcgkuclncgfqdlzqcoffyaexos/Build/Intermediates.noindex/PrecompiledHeaders/…

谷粒商城实战(008 缓存)

Java项目《谷粒商城》架构师级Java项目实战,对标阿里P6-P7,全网最强 总时长 104:45:00 共408P 此文章包含第151p-第p157的内容 简介 数据库承担落盘(持久化)工作 拿map做缓存 这种是本地缓存,会有一些问题 分布…

LeetCode每日一题之专题一:双指针 ——移动零

移动零OJ链接:283. 移动零 - 力扣(LeetCode) 题目: 解法(快排的思想:数组划分区间-数组分两块): 算法思路:在本题中,我们可以用一个 dest 指针来扫描整个数组…

Nginx 高级

文章目录 Nginx反向代理概念配置 负载均衡概念配置 动静分离概念配置 网关防盗链keepalivednginx跨域 Nginx 反向代理 概念 反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器&…

美联储,非必要,不降息

美联储“没必要、没空间、没动力”降息,也会尽量避免货币政策干扰大选,用“口头降息”代替实际调整是现实选择,市场降息预期将继续推迟和下调。 前言: 当前美国经济从各个方面看均并未表现出疲态——新增就业持续修复,…

JVM 内存溢出排查

说明:记录一次JVM内存溢出的排查过程; 场景 项目开发完成后,首次提交到测试环境。测试、产品同事反馈页面先是操作响应慢,抛出超时异常,最后直接无法使用。查看日志后得知是内存溢出。 重启服务后,我对前…

苹果开发者账号注册后生成开发证书和发布证书的流程解析

转载:注册苹果开发者账号的方法 在2020年以前,注册苹果开发者账号后,就可以生成证书。 但2020年后,因为注册苹果开发者账号需要使用Apple Developer app注册开发者账号,所以需要缴费才能创建ios证书了。 所以新政策出…