单例模式8种写法

article/2024/7/17 3:20:23

0. 为什么需要单例模式?

  • 节省内存和计算
  • 保证结果正确
  • 方便管理

使用场景:

1. 饿汉式(静态常量)—推荐指数:★★☆☆☆

优点:不会有线程安全问题。
缺点:在类加载的时候就创建对象,如果一直没使用到该对象的话,就造成了内存浪费,如果对象初始化的工作有很多,也会影响到性能。

代码展示:

//饿汉式(静态常量) ---可用
public class Singleton1 {// 类加载时就创建该实例private final static Singleton1 INSTANCE = new Singleton1();private Singleton1(){}public static Singleton1 getInstance(){return INSTANCE;}
}

2. 饿汉式(静态代码块) —推荐指数:★★☆☆☆

优缺点和第一种方式基本一致。

//饿汉式(静态代码块) ---可用
public class Singleton2 {private final static Singleton2 INSTANCE;// 以静态代码快的形式创建实例static {INSTANCE = new Singleton2();}private Singleton2(){}public static Singleton2 getInstance(){return INSTANCE;}
}

3. 懒汉式(线程不安全)—推荐指数:☆☆☆☆☆

优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:有线程安全问题。

//懒汉试(线程不安全)---不可用
public class Singleton3 {private static Singleton3 INSTANCE;private Singleton3(){}public static Singleton3 getInstance(){if (INSTANCE == null){// 当多个线程同时执行到这里,就会创建多个实例,那各自线程的实例就不是同一个了INSTANCE = new Singleton3();}return INSTANCE;}
}

4. 懒汉式(线程安全,同步方法)—推荐指数:★☆☆☆☆

优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:没有线程安全问题,但是有很大的性能问题,当多个线程同时到达getInstance()方法时,需要排队进入。

这个是在第 3 步的基础上实现的,使用 synchronized 修饰静态方法,由于加上了同步工具类,同一时间只能有一个线程操作,也使得性能下降,所以也不推荐使用:

//懒汉试(线程安全)---不推荐
public class Singleton4 {private static Singleton4 INSTANCE;private Singleton4(){}public synchronized static Singleton4 getInstance(){if (INSTANCE == null){INSTANCE = new Singleton4();}return INSTANCE;}
}

5. 懒汉式(线程不安全,同步代码块)—推荐指数:☆☆☆☆☆

优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:线程不安全,还有性能问题!多个线程在synchronized那一行排队,进入代码块后一样会创建多个对象。

这个是在第 4 步的基础上作进一步尝试,虽然性能上的问题解决了,但是又出现了线程不安全的问题,如下:

//懒汉试(线程不安全,同步代码块)---不可用
public class Singleton5 {private static Singleton5 INSTANCE;private Singleton5(){}public static Singleton5 getInstance(){if (INSTANCE == null){// 如果多个线程同时执行到这里,依然会创建多个实例synchronized (Singleton5.class){INSTANCE = new Singleton5();}}return INSTANCE;}
}

6. 懒汉式(双重检查)—推荐指数:★★★★☆

优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:复杂。

//双重检查---推荐使用
public class Singleton6 {private static Singleton6 INSTANCE;private Singleton6(){}public static Singleton6 getInstance(){if (INSTANCE == null){synchronized (Singleton6.class){//再做一次检查:由于同时只能有一个线程进入到这里,所以此时的 INSTANCE 如果还是 null,那么就再创建,创建之后有且仅有这一个实例if (INSTANCE == null){INSTANCE = new Singleton6();}}}return INSTANCE;}
}

这个优化我们利用了双重检测机制和同步锁,这种方式也称为双重同步锁单例模式,但是这个案例还是线程不安全的,大家通过代码层面的分析后,发现确实不会有线程安全问题,那问题出现在哪呢?这个其实要和对象创建步骤和JVM 指令重排挂钩,我们正常创建对象的指令步骤是这样的:

  • memory = allocate() 分配对象的内存空间
  • ctorInstance() 初始化对象,执行对应的构造方法
  • instance = memory 设置instance指向刚分配的内存

但是因为JVM和cpu优化,发生了指令重排,执行顺序如下:

  • memory = allocate() 分配对象的内存空间
  • instance = memory 设置instance指向刚分配的内存
  • ctorInstance() 初始化对象

我们可以结合代码,假如A线程进入同步代码块执行 instance = new Singleton6(),执行到“instance = memory 设置instance指向刚分配的内存”,这个时候B线程在第一次执行“if (instance == null)”,发现instance不为空,直接返回instance实例,其实线程B得到的这个实例并没有完全初始化(A还没有执行完对象的初始化步骤)就已经使用了。

那如何禁止指令重排呢,很简单,用我们前面文章提到的volatile关键字就可以了

在 INSTANCE 前加上 volatile 关键字来修饰,代码如下:

//双重检查---推荐使用
public class Singleton6 {private volatile static Singleton6 INSTANCE;private Singleton6(){}public static Singleton6 getInstance(){if (INSTANCE == null){synchronized (Singleton6.class){//再做一次检查:由于同时只能有一个线程进入到这里,所以此时的 INSTANCE 如果还是 null,那么就再创建,创建之后有且仅有这一个实例if (INSTANCE == null){INSTANCE = new Singleton6();}}}return INSTANCE;}
}

7. 静态内部类 — 推荐指数:★★★☆☆

优点:使用到的时候才会创建对象,不会造成各种资源浪费问题,线程安全。
缺点:没有太大缺点。

由于类在初始化时,并不会初始化静态内部类中的实例,所以这属于饿汉式单例:

//静态内部类 --- 推荐使用
public class Singleton7 {private Singleton7(){}private static class SingletonInstance{//JVM会保证构造方法的线程安全问题,即使多个线程同时访问 getInstance() 方法,也只会创建一个实例,这是 JVM 保证的 private static final Singleton7 INSTANCE = new Singleton7();}public static Singleton7 getInstance(){return SingletonInstance.INSTANCE;}
}

8. 枚举 — 推荐指数:★★★★★

优点:使用到的时候才会创建对象,不会造成各种资源浪费问题,线程安全。
缺点:最优方案。

//枚举 --- 生产实践推荐使用
public enum Singleton8 {INSTANCE;//方法public void whatever(){ }
}

使用的时候只需要调用Singleton8.INSTANCE ,比如这里想调用该类中的 whatever()方法,只需要执行 Singleton8.INSTANCE.whatever()

文章来源:单例模式8种写法

个人微信:CaiBaoDeCai

微信公众号名称:Java知者

微信公众号 ID: JavaZhiZhe

谢谢关注!


http://www.ngui.cc/article/show-1200636.html

相关文章

CentOS安装jdk、tomcat、apache

一、安装JDK8 centos 创建/home/software文件夹 安装好xshell与xftp并连接centos jdk官网https://www.oracle.com/java/technologies/downloads/#java8 下载 上传/home/software 解压缩 tar -zxvf jdk-8u371-linux-x64.tar.gz 重命名 mv jdk1.8.0_371 jdk8添加环境变量…

美国金融科技公司SoFi的增长难以持久,股价也将下跌

来源:猛兽财经 作者:猛兽财经 公司介绍 SoFi Technologies(SoFi)是一家来自美国的知名金融科技公司,自2011年成立以来,已成为领先的个人理财在线平台。SoFi为年轻的高收入客户提供多样化的产品和服务,包括学生和汽车贷…

sql server 字符串链接,及表连接多个值显示连接显示为一列 STUFF for xml path

sql server 字符串链接,及表连接多个值显示连接显示为一列 STUFF for xml path STUFF ( character_expression , start , length , replaceWith_expression ) 以下示例从第一个字符串 abcdef 的第 2 个位置 (b) 开始删除三个字符,然后在删除位置插入…

I.MX RT1170加密启动详解(4):OTFAD XIP加密运行代码

本节将介绍基于AES加密的OTFAD引擎,它可以在不影响AES-128-CTR性能的情况下实时解密数据。OTFAD包括对AES密钥展开机制的完整硬件支持,它可以解密最多4个唯一的AES上下文。每个上下文都有一个用户定义的128位的Image Encryption Key(IEK)、一个64位的计数…

快手三面全过了,却因为背调时leader手机号造假,导致offer作废了!

这是一个悲伤的故事: 快手本地三面全过了,但因为背调时leader手机号造假,导致offer作废了。 楼主感叹:大家背调填写信息时,一定要慎重再慎重,不要重复他的悲剧! 网友愤慨,照这么说&a…

【每日一题Day225】L1156单字符重复子串的最大长度 | 贪心+滑动窗口

单字符重复子串的最大长度【L1156】 如果字符串中的所有字符都相同,那么这个字符串是单字符重复的字符串。 给你一个字符串 text,你只能交换其中两个字符一次或者什么都不做,然后得到一些单字符重复的子串。返回其中最长的子串的长度。 思路&…

到底什么是“云手机”?

今天这篇文章,我们来聊一个很有趣的东东——云手机。 说到云手机,有些童鞋可能并不会觉得陌生。是的,它确实并不是一个新名词。早在2011年左右,国内就有厂商推出了云手机的概念。掐指一算,至今已经有12个年头了。 大家…

windows系统编译的Qt程序转到国产化麒麟linux中编译

团队自研股票软件,关威信共总号:QStockView,下载 1.1 windows系统编译的Qt程序转到国产化麒麟linux中编译 (1)把Vs工程项目文件导入到Linux中 首先把vs的工程拷贝到linux里面(可以用虚拟机的共享文件夹…

chatgpt赋能python:Python创建venv的完全指南

Python创建venv的完全指南 在Python开发中,虚拟环境是一个非常有用的工具。它可以让我们在同一台计算机上拥有多个Python环境,而不会互相干扰。在本文中,我们将介绍如何使用Python创建venv(虚拟环境)。 什么是venv&a…

【Dubbo】Dubbo架构的演进过程分析

📫作者简介:小明java问道之路,2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于…