Java创建对象的最佳方式:单例模式(Singleton)

news/2024/5/14 8:41:23

前言

单例模式是java中最简单的设计模式之一,属于创建式模式,提供了一种创建对象的最佳方式。

具体而言,单例模式涉及到一个具体的类,这个类可以确保只有单个对象被创建。它包含一个访问其唯一对象的方法,供外部直接调用,而不需要创建这个类的示例。

简而言之,可以不再new一个他的实例,而是直接调用方法。

实现

单例模式分为两种:

  • 饿汉式:类加载时就会导致该单例对象被创建
  • 懒汉式:类加载不会导致该单例对象被创建,而是首次使用时才会

饿汉式

静态变量方式

public class Singleton {// 私有构造方法private Singleton() {}// 静态方式创建该类的对象private static Singleton instance = new Singleton();// 对外提供静态方法,用于获取该对象public static Singleton getInstance() {return instance;}
}

将对象的实例创建为静态,保证了实例永远只有一份,且在该类加载时就会立即创建在jvm内存的方法区,程序运行期间永久存在,因此当对象太大时会造成浪费。

静态代码块方式

public class Singleton {// 私有构造方法private Singleton() {}// 静态创建该类对象private static Singleton instance;static {instance = new Singleton();}// 对外提供静态方法,用于获取该对象public static Singleton getInstance() {return instance;}
}

对象的创建在代码块中,也是随着类的加载而创建,两种方式基本相同。

 懒汉式

从名字出发,饿汉式会一直饿着,需要不断有食物,即对象一直存在。

懒汉式则是只有在饿的时候才会寻找食物,即请求对象实例。

所以,只要将饿汉式的创建对象放到getInstance方法中,即只有在调用该方法时才创建,我们就完成了懒加载的效果。

更改初始化时机

public class Singleton {// 私有构造方法private Singleton() {}// 静态创建该类对象private static Singleton instance;// 对外提供静态方法,用于获取该对象public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

但这样的代码是线程不安全的,可以设想,如果有两个线程同时调用getInstance方法,他们都发现了instance == null,那么此时就会有两个实例化的instance,这就违背了单例模式的初衷了。

 最容易想到的方法就是给getInstance方法上锁。

上锁:防止初始化多次

public class Singleton {// 私有构造方法private Singleton() {}// 静态创建该类对象private static Singleton instance;// 上锁public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

但此处依然有优化的空间,如果instance不为空,让线程调用就好了,不一定要持有锁,因此我们引申出双重检查锁模式。

优化:双重锁模式

public class Singleton {// 私有构造方法private Singleton() {}// 静态创建该类对象private static Singleton instance;// 上锁public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {// 线程拥有锁之后,再判断是否为空if (instance == null) {instance = new Singleton();}}}return instance;}
}

为什么要两次判断是否为空?

例如有ab两个线程,当a先获得锁并初始化instance实例后,b获得锁,如果此时不判空,则会重复初始化,因为此时两者都进入了锁之内了,上一个判空已经过时。

但是,jvm在实例化对象的时候,会进行优化和指令重排序操作,多线程的情况下,可能会产生空指针,此时我们可以使用volatile关键字。

使用volatile关键字修饰的变量,可以保证其指令执行的顺序与我们写明的顺序一致,最终代码如下:

public class Singleton {// 私有构造方法private Singleton() {}// 静态创建该类对象private static volatile Singleton instance;// 上锁public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {// 线程拥有锁之后,再判断是否为空if (instance == null) {instance = new Singleton();}}}return instance;}
}

最终进化:枚举实现

在java1.5之后,使用java实现单例模式的方式多了一种枚举。

public enum Singleton {INSTANCE;public void doSomething() {System.out.println("这是枚举类型的单例模式!");}
}

我们可以对枚举方式实现的实例进行反编译,可以得到如下的代码:

public final class Singleton extends Enum {public static Singleton[] values() {return (Singleton[])$VALUES.clone();}public static Singleton valueOf(String name) {return (Singleton)Enum.valueOf(com/qgn/mianshi/main/Singleton, name);}private Singleton(String s, int i) {super(s, i);}public static final Singleton INSTANCE;private static final Singleton $VALUES[];static {INSTANCE = new Singleton("INSTANCE", 0);$VALUES = (new Singleton[] {INSTANCE});}
}

可见Singleton继承了Enum,并重写了Enum类中的一些方法,基本上这些方法都是静态的。

这种实现方式的优点在于,不需要做额外的操作去保证对象单一性与线程安全,同时使用枚举可以防止调用者使用反射、反序列化强制生成多个单例对象,破坏单例模式。

但是如果没使用枚举时,要怎么防止反射和反序列化破坏单例呢?

防止反射、反序列化破坏单例模式

防反射

反射是一种暴力获取对象实例的方法,可以直接获取private修饰的构造函数,所以在放反射上只能被动防御。

既然能访问构造函数,那我们也可以在构造函数中建立防御机制。

public class Singleton  {private static volatile boolean flag = false;//私有构造方法private Singleton() {synchronized (Singleton.class){if (flag){throw new RuntimeException();}flag = true;}}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}//对外提供静态方法获取该对象public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

防反序列化

在readObject()方法的调用栈的底层方法中有这么两个方法:
hasReadResolveMethod:
表示如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true。

所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。

public class Singleton implements Serializable {//私有构造方法private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}//对外提供静态方法获取该对象public static Singleton getInstance() {return SingletonHolder.INSTANCE;}private Object readResolve() {return SingletonHolder.INSTANCE;}
}


http://www.mrgr.cn/p/31344335

相关文章

【论文阅读】互连网络的负载平衡路由算法 (RLB RLBth)

前言Oblivious Load Balancing 不经意路由负载平衡 1. oblivious routing 不经意/无关路由的背景知识 1. oblivious routing, adaptive routing & minimal/non-minimal routing algorithms 2. RLB and RLBth 1. Motivation of Balancing load 平衡负载的动机2. 一维 ring 的…

【论文阅读】ViTAE:Vision transformer advanced by exploring intrinsic inductive bias

ViTAE:Vision transformer advanced by exploring intrinsic inductive bias 论文地址摘要:简介:3 方法论3.1 重温视觉变压器3.2 ViTAE3.3 缩减单元3.4 Normal cell3.5 模型细节 4 训练4.1 Implementation details4.2 Comparison with the state-of-the-…

Python高阶--闭包

闭包作用:可以用来在一个函数与一组私有变量之间创建关联关系,在给定函数被多次调用的过程中,这些私有变量能够保持其持久性(保存运行环境与变量的状态) 闭包的特征:1.必须要有函数的嵌套,而且外层函数必须返回内层函数。外层函数相当于给内层函数提供了一个包装起来的运…

实验五 Spark SQL编程初级实践

Spark SQL编程初级实践 Spark SQL基本操作 将下列JSON格式数据复制到Linux系统中,并保存命名为employee.json。 { "id":1 , "name":" Ella" , "age":36 } { "id":2, "name":"Bob","a…

数据结构和算法:贪心

贪心算法 贪心算法是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期获得全局最优解。 贪心算法和动态规划都常用于解决优化问题。它们之间存在一…

【数据结构】最小生成树(Prim算法、Kruskal算法)解析+完整代码

5.1 最小生成树 定义 对一个带权连通无向图 G ( V , E ) G(V,E) G(V,E),生成树不同,每棵树的权(即树中所有边上的权值之和)也可能不同。 设R为G的所有生成树的集合,若T为R中边的权值之和最小的生成树,则T称…

使用大卫的k8s监控面板(k8s+prometheus+grafana)

问题 书接上回,对EKS(AWS云k8s)启用AMP(AWS云Prometheus)监控AMG(AWS云 grafana),上次我们只是配通了EKSAMPAMG的监控路径。这次使用一位大卫老师的grafana的面板,具体地址如下: ht…

【idea】idea 中 git 分支多个提交合并一个提交到新的分支

一、方法原理讲解 我们在 dev 分支对不同的代码文件做了多次提交。现在我们想要把这些提交都合并到 test 分支。首先我们要明白四个 git 操作, commit:命令用于将你的代码变更保存到本地代码仓库中,它创建了一个新的提交(commit…

干货整理:好用的文件加密软件有哪些

说到文件加密,想必大家都很熟悉,文件加密已经普遍应用,文件加密是一种重要的安全措施,可以确保数据的机密性、完整性和可用性,降低因数据泄露或丢失带来的风险。 下面小编给大家分享几款常用的加密软件,大…

Spark和Hadoop的安装

实验内容和要求 1.安装Hadoop和Spark 进入Linux系统,完成Hadoop伪分布式模式的安装。完成Hadoop的安装以后,再安装Spark(Local模式)。 2.HDFS常用操作 使用hadoop用户名登录进入Linux系统,启动…

【Flink入门修炼】2-3 Flink Checkpoint 原理机制

如果让你来做一个有状态流式应用的故障恢复,你会如何来做呢? 单机和多机会遇到什么不同的问题? Flink Checkpoint 是做什么用的?原理是什么? 一、什么是 Checkpoint? Checkpoint 是对当前运行状态的完整记…

IDM下载器_Internet Download Manager 6.42.7

网盘下载 IDM下载器是一款针对互联网所打造的下载管理器。IDM下载器能将下载速度提高5倍,恢复因丢失的连接,网络问题,计算机关闭或意外断电而重新启动中断或中断的下载。IDM下载器还可支持所有流行的浏览器,以使用独特的“高级浏…

论文解读:(VPT)Visual Prompt Tuning

文章汇总 要解决的问题 大型模型应用于下游任务本身就存在挑战。最明显的(通常也是最有效的)适应策略是对预先训练好的模型进行全面的端到端微调。 动机 只微调参数的一个子集 解决的办法 只在输入空间中引入少量特定于任务的可学习参数,而在下游训练期间冻结…

03-JAVA设计模式-策略模式

策略模式 什么是策略模式 策略模式(Strategy Pattern)是行为设计模式之一,它使你能在运行时改变对象的行为。在策略模式中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为模式。 在策略模式中,…

Matlab 使用subplot绘制多个子图,一元拟合

实现效果: clc; clear;filename sri.xlsx; % 确认文件路径data readtable(filename); datavalue data{:,2:end}; datavalue datavalue;fig figure(Position, [0, 0, 1500, 900]); indexString ["(a)","(b)","(c)","(d)&qu…

go语言并发实战——日志收集系统(八) go语言操作etcd以及利用watch实现对键值的监控

有关包的安装 我们要实现go语言对第三方包的操作需要我们下载第三方包go.etcd.io,下载命令: go get go.etcd.io/etcd/client/v3 ectd的put与get操作 相关函数说明与示例 我们想实现对etcd进行简单的操作的步骤还是比较简单的,在我上一篇文…

Spring Boot 如何实现缓存预热

Spring Boot 实现缓存预热 1、使用启动监听事件实现缓存预热。2、使用 PostConstruct 注解实现缓存预热。3、使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。4、通过实现 InitializingBean 接口,并重写 afterPropertiesSet 方法实现缓存预热。 1、使用…

Linux学习之路 -- 进程篇 -- 自定义shell的编写

前面介绍了进程程序替换的相关知识&#xff0c;接下来&#xff0c;我将介绍如何基于前面的知识&#xff0c;编写一个简单的shell&#xff0c;另外本文的所展示的shell可能仅供参考。 目录 <1>获取用户的输入和打印命令行提示符 <2>切割字符串 <3>执行这个…

分析和比较深度学习框架 PyTorch 和 Tensorflow

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 深度学习作为人工智能的一个重要分支&#xff0c;在过去十年中取得了显著的进展。PyTorch 和 TensorFlow 是目前最受欢迎、最强大的两个深度学习框架&#xff0c;它们各自拥有独特的特点和优势。 1. Py…

Vue+Echarts 实现中国地图和飞线效果

目录 实现效果准备 实现效果 在线预览&#xff1a;https://mouday.github.io/vue-demo/packages/china-map/dist/index.html 准备 高版本的echarts&#xff0c;不包含地图数据&#xff0c;需要自己下载到项目中 1、地图数据下载 https://datav.aliyun.com/portal/school/at…