5种方式实现 Java 单例模式
前言
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
饿汉单例
是否多线程安全:是
是否懒加载:否
正如名字含义,饿汉需要直接创建实例。
public class EhSingleton {private static EhSingleton ehSingleton = new EhSingleton();private EhSingleton() {}public static EhSingleton getInstance(){return ehSingleton;}
}
缺点: 类加载就初始化,浪费内存
优点: 没有加锁,执行效率高。还是线程安全的实例。
懒汉单例
懒汉单例,在类初始化不会创建实例,只有被调用时才会创建实例。
非线程安全的懒汉单例
是否多线程安全:否
是否懒加载: 是
public class LazySingleton {private static LazySingleton ehSingleton;private LazySingleton() {}public static LazySingleton getInstance() {if (ehSingleton == null) {ehSingleton = new LazySingleton();}return ehSingleton;}}
实例在调用 getInstance
才会创建实例,这样的优点是不占内存,在单线程模式下,是安全的。但是多线程模式下,多个线程同时执行 if (ehSingleton == null)
结果都为 true
,会创建多个实例,所以上面的懒汉单例是一个线程不安全的实例。
加同步锁的懒汉单例
是否多线程安全:是
是否懒加载: 是
为了解决多个线程同时执行 if (ehSingleton == null)
的问题,getInstance
方法添加同步锁,这样就保证了一个线程进入了 getInstance
方法,别的线程就无法进入该方法,只有执行完毕之后,其他线程才能进入该方法,同一时间只有一个线程才能进入该方法。
public class LazySingletonSync {private static LazySingletonSync lazySingletonSync;private LazySingletonSync() {}public static synchronized LazySingletonSync getInstance() {if (lazySingletonSync == null) {lazySingletonSync =new LazySingletonSync();}return lazySingletonSync;}}
这样配置虽然保证了线程的安全性,但是效率低,只有在第一次调用初始化之后,才需要同步,初始化之后都不需要进行同步。锁的粒度太大,影响了程序的执行效率。
双重检验懒汉单例
是否多线程安全:是
是否懒加载:是
使用 synchronized
声明的方法,在多个线程访问,比如A线程访问时,其他线程必须等待A线程执行完毕之后才能访问,大大的降低的程序的运行效率。这个时候使用 synchronized
代码块优化执行时间,减少锁的粒度。
双重检验首先判断实例是否为空,然后使用 synchronized (LazySingletonDoubleCheck.class)
使用类锁,锁住整个类,执行完代码块的代码之后,新建了实例,其他代码都不走 if (lazySingletonDoubleCheck == null)
里面,只会在最开始的时候效率变慢。而 synchronized
里面还需要判断是因为可能同时有多个线程都执行到 synchronized (LazySingletonDoubleCheck.class)
,如果有一个线程线程新建实例,其他线程就能获取到 lazySingletonDoubleCheck
不为空,就不会再创建实例了。
public class LazySingletonDoubleCheck {private static LazySingletonDoubleCheck lazySingletonDoubleCheck;private LazySingletonDoubleCheck() {}public static LazySingletonDoubleCheck getInstance() {if (lazySingletonDoubleCheck == null) {synchronized (LazySingletonDoubleCheck.class) {if (lazySingletonDoubleCheck == null) {lazySingletonDoubleCheck = new LazySingletonDoubleCheck();}}}return lazySingletonDoubleCheck;}
}
静态内部类
是否多线程安全:是
是否懒加载:是
外部类加载时,并不会加载内部类,也就不会执行 new SingletonHolder()
,这属于懒加载。只有第一次调用 getInstance()
方法时才会加载 SingletonHolder
类。而静态内部类是线程安全的。
静态内部类为什么是线程安全
静态内部类利用了类加载机制的初始化阶段 方法,静态内部类的静态变量赋值操作,实际就是一个 方法,当执行 getInstance()
方法时,虚拟机才会加载 SingletonHolder
静态内部类,
然后在加载静态内部类,该内部类有静态变量,JVM会改内部生成方法,然后在初始化执行方法 —— 即执行静态变量的赋值动作。
虚拟机会保证 方法在多线程环境下使用加锁同步,只会执行一次 方法。
这种方式不仅实现延迟加载,也保障线程安全。
public class StaticClass {private StaticClass() {}private static class SingletonHolder {private static final SingletonHolder INSTANCE = new SingletonHolder();}public static final SingletonHolder getInstance() {return SingletonHolder.INSTANCE;}
}
总结
- 饿汉单例类加载就初始化,在没有加锁的情况下实现了线程安全,执行效率高。但是无论有没有调用实例都会被创建,比较浪费内存。
- 为了解决内存的浪费,使用了懒汉单例,但是懒汉单例在多线程下会引发线程不安全的问题。
- 不安全的懒汉单例,使用
synchronized
声明同步方法,获取实例就是安全了。 synchronized
声明方法每次线程调用方法,其它线程只能等待,降低了程序的运行效率。- 为了减少锁的粒度,使用
synchronized
代码块,因为只有少量的线程获取实例,实例是null,创建实例之后,后续的线程都能获取到线程,也就无需使用锁了。可能多个线程执行到synchronized
,所以同步代码块还需要再次判断一次。 - 静态内部类赋值实际是调用 方法,而虚拟机保证 方法使用锁,保证线程安全。
5种方式实现 Java 单例模式相关推荐
- java向MySQL插入当前时间的四种方式和java时间日期格式化的几种方法(案例说明)...
转载地址:http://www.devba.com/index.php/archives/4581.html java向MySQL插入当前时间的四种方式和java时间日期格式化的几种方法(案例说明); ...
- java直接调用复制文件,java中文件复制的4种方式,java文件的复制
java中文件复制的4种方式,java文件的复制 今天一个同事问我文件复制的问题,他一个100M的文件复制的指定目录下竟然成了1G多,吓我一跳,后来看了他的代码发现是自己通过字节流复制的,定义的字节数 ...
- mysql java 日期格式化_(转)java向MySQL插入当前时间的四种方式和java时间日期格式化的几种方法(案例说明)...
java向MySQL插入当前时间的四种方式和java时间日期格式化的几种方法(案例说明);部分资料参考网络资源 1. java向MySQL插入当前时间的四种方式 第一种:将java.util.Date ...
- java加载properties文件的几种方式,java高级面试笔试题
我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码或搜索下图红色VX号,加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起 ...
- java excel生成_两种方式实现java生成Excel
Web应用中难免会遇到需要将数据导出并生成excel文件的需求.同样,对于本博客中的总结,也是建立在为了完成这样的一个需求,才开始去了解其实现形式,并且顺利完成需求的开发,先将实现过程总结于此.本博文 ...
- 读取Java文件到byte数组的三种方式及Java文件操作大全(包括文件加密,String加密)
读取Java文件到byte数组的三种方式 package zs;import java.io.BufferedInputStream; import java.io.ByteArrayOutputSt ...
- 【第21期】以实际项目作驱动,换种方式学Java
Java作为OOP(面向对象编程)的集大成者,融合了其他语言的诸多优点,让开发者体验到了函数式编程的精妙及强大之处. 随着这几年开源社区的蓬勃发展,越来越多的组件.框架.方案如雨后春笋般涌现,现代工程 ...
- pdf转图片的两种方式(java)
最近在工作中遇到业务需要把pdf转成图片供前台预览,在网上查了一些方法,有的能用,有的缺东西,时间又比较急,没时间去看.在这里我整理了两种亲测可行的方式,节省大家时间带备忘: 1.pdf按页数 ...
- SpringBoot中使用AMQ的两种方式(Java配置、注解方式)
Java配置方式使用AMQ 1. 引入依赖(pom.xml) <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns ...
- python网络通信的几种方式_两种方式,java=python,使用py4j进行通信
我使用py4j实现python和java之间的通信,能够从java端调用python方法.但从python我不能发送任何对象或调用java方法.这是我试过的密码.在 我的java代码:public i ...
最新文章
- 联想拯救者y7000怎么配置Java环境_联想拯救者y7000重装系统教程
- 轻松搞定Retrofit不同网络请求方式的请求参数配置,及常用注解使用
- mysql 新增更新_MySQL新增数据,存在就更新,不存在就添加(转帖加实测)
- python打开摄像头获取图片_Python基于opencv调用摄像头获取个人图片的实现方法
- python3 发送邮件
- OpenCV 从文件中读取视频并播放
- 对于c语言int类型和float,以及double类型表示范围的计算
- springboot整合redis操作缓存(将查询到的数据放在缓存中)
- AAAI 2018 论文 | 蚂蚁金服公开最新基于笔画的中文词向量算法
- async 与 await 的用法详解
- 中国数码相机镜头行业市场供需与战略研究报告
- Appium探索—Mac OS Python版
- 猴子排序的期望复杂度推导(雾)
- “牙医”教你用450行Go代码自制编程语言 | Gopher Daily (2021.01.15) ʕ◔ϖ◔ʔ
- dos2unix命令
- 静态代理和动态代理的区别,什么场景使用?
- 牛客网 - [牛客假日团队赛6]Charm Bracelet(01背包)
- 《人机交互技术》 第八章 移动界面设计
- linux dot命令,DOT语言使用笔记(1)
- 关于java文件乱码解决
热门文章
- centos7服务器安装无线网卡,CentOS 7 安装无线网卡驱动方法实例教程
- 局域网文件服务器带宽,高带宽局域网
- 镜像css3,CSS下镜像翻转(水平/垂直翻转)
- TcaplusDB X 光与夜之恋,来谈一场高沉浸式的恋爱吧
- Inno Setup 6.0.3+ 简体中文语言包
- 如何Altium Designer中输出元件清单(BOM表格)
- 易语言删除c盘源码,易语言基础教程利用API删除自身及子文件
- 深度Linux安装红警2,深度Deepin Linux v20 Beta下玩红警
- windows11无法添加朝鲜语输入法
- python谐音梗_谐 音 梗 生 成 器