【深入理解设计模式】单例设计模式

单例设计模式

在这里插入图片描述

概念:

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。

单例设计模式是一种创建型设计模式,其主要目的是确保类在应用程序中的一个实例只有一个。这意味着无论在应用程序的哪个位置请求该类的实例,都将获得同一个实例。这种模式通常用于控制某些共享资源的访问,或者在整个应用程序中管理唯一的状态。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的实现:

单例设计模式分类两种:

  1. 饿汉式:类加载就会导致该单实例对象被创建
  2. 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式实现方式:
饿汉式实现1 - 静态变量方式
/**
* @author OldGj 
* @version v1.0
* @apiNote 单例设计模式 - 饿汉式:静态变量实现
*/
public class Singleton {// 1.构造方法私有化private Singleton() {}// 2.在成员位置创建本类的对象private static final Singleton instance = new Singleton();// 3.对外界提供可以访问到自身创建的对象的静态方法public static Singleton getInstance(){return instance;}
}

注意:
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

饿汉式实现2 - 静态代码块方式

拓展:静态代码块在类加载过程的初始化阶段执行。
类的加载过程分为: 加载 -> 链接验证准备解析) -> 初始化

/*** @author OldGj * @version v1.0* @apiNote 单例设计模式 - 饿汉式:静态代码块实现*/
public class Singleton {// 1.构造器私有化private Singleton(){}// 2.声明静态全局变量private static Singleton instance;// 3.在静态代码块中进行赋值static {instance = new Singleton();}// 4.对外提供可以获取本类对象的静态方法public static Singleton getInstance(){return instance;}
}

注意:
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。

饿汉式实现3 - 枚举

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/**
* @author OldGj 
* @version v1.0
* @apiNote 单例模式 - 饿汉式 > 利用枚举实现 <
*/
public enum Singleton {INSTANCE;
}

注意:
枚举方式属于饿汉式方式。如果在不考虑饿汉式存在内存浪费问题的情况下使用枚举类型创建单例模式是最好的单例实现方式,因为枚举类型是天然的线程安全的,并且实现方式简单,只会被加载一起,而且是所有单例实现方式中,唯一一种不会被破坏的实现方式。

懒汉式实现方式:
懒汉式实现1 - 会有线程安全问题
/**
* @author OldGj
* @version v1.0
* @apiNote 单例设计模式 - 懒汉式 > 线程不安全 <
*/
public class Singleton {// 1.构造器私有化private Singleton() {}// 2.在成员位置创建本类的对象private static Singleton instance;// 3.对外提供可以获取到本类对象的静态方法public static Singleton getInstance() {// 只有在instance变量还没有实例化对象的时候,才实例化一个对象if (instance == null) {// 线程1 -> 等待// 线程2,分配到CPU,实例化了一个Singleton实例并赋值给instance变量// 线程1,因为已经进入判断,因此线程1又实例化了一个Singleton对象,不符合单例模式instance = new Singleton();}return instance;}
}

注意:
该从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。

懒汉式实现2 - synchronized同步锁解决线程安全问题
/*** @author OldGj * @version v1.0* @apiNote 单例设计模式 - 懒汉式  > 线程安全 <*/
public class Singleton {// 1.构造器私有化private Singleton (){}// 2.在成员位置声明Singleton类型的静态变量private static Singleton instance;// 3.对外提供一个可以获取本类对象的静态方法// 并且将该方法使用synchronized同步锁加锁,确保实例化过程中的线程安全public static synchronized Singleton getInstance(){if(instance==null){instance = new Singleton();}return instance;}
}

注意:
该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。

懒汉式实现3 - 双重检查锁

再来讨论一下懒汉模式中加锁的问题,对于 getInstance() 方法来说,绝大部分的操作都是读操作读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式


/*** @author OldGj * @version v1.0* @apiNote 单例设计模式 - 懒汉式 <b> > 双重检查锁模式 < <b/><br/>*          <p>多线程环境下,不会出现线程安全问题,也不会有性能问题*推荐使用*<p/>*/
public class Singleton {// 1.构造器私有化private Singleton(){}// 2.在成员位置声明Singleton类型的静态变量并用volatile关键字修饰private static volatile Singleton instance;// 3.对外提供可以获取本类对象的静态方法,并在方法内采用双重检查锁保证线程安全public static Singleton getInstance(){// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例if(instance==null){synchronized (Singleton.class){// 抢到锁之后再次判断是否为nullif(instance==null){instance = new Singleton();}}}return instance;}}

注意:
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

懒汉式实现4 - 静态内部类方式

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

/*** @author OldGj * @version v1.0* @apiNote 单例设计模式 - 懒汉式 > 静态内部类实现 <*/
public class Singleton {/*** 静态内部类单例模式中实例由内部类创建,* 由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载,* 并初始化其静态属性。静态属性由于被 `static` 修饰,保证只被实例化一次,并且严格保证实例化顺序。*/// 1.构造器私有化private Singleton() {}// 2.创建静态内部类,并且在静态内部类中创建外部类的实例private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}// 3.对外提供可以获得本类实例的静态方法public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

说明:

​ 第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance(),虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。

小结:

​ 静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

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

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

相关文章

左右联动布局效果

效果图&#xff1a; <template><el-dialog :modelValue"modelValue" :before-close"close" fullscreen :close-on-click-modal"false"><div class"farmer_detail"><div class"info_content"><di…

数论 - 高斯消元

文章目录 一、高斯消元解线性方程组1.题目描述输入格式输出格式数据范围输入样例&#xff1a;输出样例&#xff1a; 2.算法思路3.代码 二、高斯消元解异或线性方程组1.题目描述输入格式输出格式数据范围输入样例&#xff1a;输出样例&#xff1a; 2.算法思路3.代码 数学上&…

微信小程序swiper 视频中间大,两边小,轮播滑到中间视频自动播放组件教程

静态效果&#xff1a; 进入下面小程序可以体验效果&#xff0c;点击底部 看剧 栏目 一、创建小程序组件 二、代码 1、WXML <view class"swiper-wrapper" style"background-image:url(/asset/image/hot-banner.jpg);background-size: 100% 100%;">…

USB-C音频转接器:实现边充电边听歌的新选择 | LDR6020P

随着科技浪潮的推进&#xff0c;Type-C接口已逐渐成为电子设备的主流选择&#xff0c;以其正反随意插、高速传输和强大功能等独特优势&#xff0c;在日常生活中占据越来越重要的地位。而Type-C音频转接器&#xff0c;作为连接Type-C接口与音频设备的桥梁&#xff0c;正引领着音…

appium实现自动化测试原理

目录 1、Appium原理 1.1、Android Appium原理图文解析 1.1.2、原理详解 1.1.2.1、脚本端 1.1.2.2、appium-server 1.1.2.3、中间件bootstrap.jar 1.1.2.4、驱动引擎uiautomator 1.2、 IOS Appium原理 1、Appium原理 1.1、Android Appium原理图文解析 执行测试脚本全过…

Windows制作Ubuntu的U盘启动盘

概要&#xff1a; 本篇演示在Windows10中制作Ubuntu22.04的U盘启动盘 一、下载Ubuntu22.04的iso文件 在浏览器中输入https://ubuntu.com去Ubuntu官网下载Ubuntu22.04的iso文件 二、下载Ultraiso 在浏览器中输入https://www.ultraiso.com进入ultraiso官网 点击FREE TRIAL&a…

阿里云服务器操作系统有哪些?如何选择?

阿里云服务器镜像怎么选择&#xff1f;云服务器操作系统镜像分为Linux和Windows两大类&#xff0c;Linux可以选择Alibaba Cloud Linux&#xff0c;Windows可以选择Windows Server 2022数据中心版64位中文版&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说下阿里云服务器操…

CDP和Chrome

CDP和Chrome CDP和WebDriver Protocol WebDriver和 Chrome DevTools Protocol&#xff08;CDP&#xff09; 是用于自动化浏览器的两个主要协议&#xff0c;大多数的浏览器自动化工具都是基于上述其中之一来实现的。可以通过这两种形式来和浏览器交互&#xff0c;通过代码来控…

Python:多线程(threading)的学习和使用详解

概要 在Python中,threading库提供了一种简单且方便的方式来实现多线程编程。通过使用线程,可以在程序中并行执行多个任务,提高程序的性能和响应性。 了解线程 线程是程序执行的最小单元,是操作系统能够进行运算调度的基本单位。与进程不同,线程在同一进程下共享相同的内…

Redis篇----第六篇

系列文章目录 文章目录 系列文章目录前言一、Redis 的持久化机制是什么?各自的优缺点?二、Redis 常见性能问题和解决方案:三、redis 过期键的删除策略?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章…

极狐GitLab 如何配置多个 LDAP?

本文仅适用于极狐GitLab私有化部署场景。 场景化痛点 极狐GitLab 的多 LDAP 接入功能解决了企业在以下场景中可能遇到的痛点&#xff1a; 多个组织/部门的整合&#xff1a;在大型企业或跨国公司中&#xff0c;往往存在多个组织或部门&#xff0c;它们可能拥有独立的 LDAP 服务…

【day01】每天三道 java后端面试题:JDK、JRE和JVM | 字节码 | ACID

文章目录 1. JDK, JRE, JVM分别是什么&#xff1f;有什么区别&#xff1f;2. 什么是字节码&#xff1f;采用字节码的最大好处是什么&#xff1f;3. 什么是数据库事务&#xff1f;讲一下事务的ACID特性。 1. JDK, JRE, JVM分别是什么&#xff1f;有什么区别&#xff1f; 答题思路…