【设计模式】单例模式是什么?如何实现单例模式?单例模式常见问题?
目录
- 1. 单例模式
- 1. 单例模式的三要素
- 2. 优点
- 3. 单例的实现方式(思路:思考一下Java创建对象的几种方式?好像解题思路也不对。。。基本都是通过new出来的)
- 1. 饿汉模式单例(在类加载时会初始化)
- 1. new方式
- 2. 静态代码块
- 3. 枚举实例化
- 1. 优点:防止反射问题、防止反序列化问题、防止clone
- 2. 枚举反编译后的结果:
- 3. 枚举为什么不能通过反射创建实例?(要看源码Constructor的newInstance方法)
- 4. 枚举为什么可以避免序列化问题?(重写了readObject方法)
- 5. 枚举为什么可以避免clone问题?(重写了clone方法)
- 2. 懒汉模式单例(在类加载时不初始化,调用获取单例的方法时候再初始化)
- 1. 在静态方法中使用双重校验(保证只创建一个实例)
- 2. 在静态方法中调用静态内部类
- 3. 基于容器实现单例(单例注册表)
- Spring创建Bean的方式(有兴趣可以深入了解下)
- 4. 非枚举创建的单例,存在的问题
- 1. 可以使用反射机制调用私有构造器,创建第二个实例
- 2. 序列化前后,实例发生改变
- 3. 可以通过clone拷贝的方式去创建一个新的实例
- 5. 单例模式常见面试题(TODO)
- 1. 哪些类是单例模式的后续类?在Java中哪些类会成为单例?
- 2. 你能在Java中编写单例里的getInstance()的代码?
- 3. 在getInstance()方法上同步有优势还是仅同步必要的块更优优势?你更喜欢哪个方式?
- 4. 什么是单例模式的延迟加载或早期加载?你如何实现它?
- 5. JDK中的单例模式的实例有哪些?(好问题)
- 1.JDK中的饿汉模式(JVM启动时实例化)
- 1. java.lang.Runtime类(JVM进程启动的,供所有线程使用)
- 1. 源码如下:
- 2. 为什么Runtime类要用饿汉模式?
- 2. JDK中的懒汉模式(GUI包下)(JVM启动时不初始化)
- 1. java.awt.Toolkit#getDefaultToolkit()
- 2. java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()
- 3. java.awt.Desktop#getDesktop()
- 6. 单例模式的两次检查锁是什么?
- 7. 你如何阻止使用clone()方法创建单例实例的另一个实例?
- 8. 如何阻止通过使用反射来创建单例类的另一个实例?
- 9. 如何阻止通过使用序列化来创建单例类的另一个实例?
- 10. Java中的单例模式什么时候是非单例?
1. 单例模式
保证一个类只有唯一的一个实例,并提供一个全局的访问点。
1. 单例模式的三要素
- 私有静态实例引用
- 私有的构造方法
- 返回静态实例的静态公有方法
白话文:1个构造方法 + 1个引用(不一定有实例)+ 1个获取单例的方法
2. 优点
- 在内存中只有一个对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高性能;(只有一个实例)
- 避免对共享资源的多重占用,简化访问;
- 为整个系统提供一个全局访问点。(提供了获取单例的方法)
3. 单例的实现方式(思路:思考一下Java创建对象的几种方式?好像解题思路也不对。。。基本都是通过new出来的)
1. 饿汉模式单例(在类加载时会初始化)
饿汉在类加载的时候就会初始化,所以不会有线程安全的问题,getInstance不需要有任何操作,直接拿到instance就行
1. new方式
public class SingletonDemo {// 在类加载的时候直接new这个实例private static SingletonDemo instance = new SingletonDemo();private SingletonDemo(){}public static SingletonDemo getInstance(){return instance;}
}
在类加载的时候直接new这个实例
2. 静态代码块
public class SingletonDemo {private static SingletonDemo instance = null;static{instance = new SingletonDemo();}private SingletonDemo(){}public static SingletonDemo getInstance(){return instance;}
}
静态代码块在类加载的时候就会执行
题外话:静态代码块相比直接new的方式,到底好在哪里?TODO
3. 枚举实例化
public enum SingletonDemo {INSTANCE;public SingletonDemo getInstance(){return INSTANCE;}
}
1. 优点:防止反射问题、防止反序列化问题、防止clone
2. 枚举反编译后的结果:
public final class SingletonDemo extends Enum<SingletonDemo> {public static final SingletonDemo SINGLETONDEMO;public static SingletonDemo[] values();public static SingletonDemo valueOf(String s);static {};
}// 这是Enum类
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {// 名称private final String name;public final String name() {return name;}// 序号private final int ordinal;public final int ordinal() {return ordinal;}protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}
}
3. 枚举为什么不能通过反射创建实例?(要看源码Constructor的newInstance方法)
源码分析:Class类通过反射调用Constructor类的newInstance方法创建实例
public final class Constructor<T> extends Executable {@CallerSensitivepublic T newInstance(Object ... initargs)throws InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, null, modifiers);}}// 原因在这里:如果这个Class类是属于Enum的话,则会报异常,创建失败if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");ConstructorAccessor ca = constructorAccessor; // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@SuppressWarnings("unchecked")T inst = (T) ca.newInstance(initargs);return inst;}
}
如果这个Class类是属于Enum的话,则会报异常,创建失败
4. 枚举为什么可以避免序列化问题?(重写了readObject方法)
反序列化创建实例的本质是调用Object的readObject方法,而Enum类的方法一调用就会报异常
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {/*** prevent default deserialization*/private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {// 原因在这里:直接报“不能序列化枚举”异常throw new InvalidObjectException("can't deserialize enum");}
}
原因是:Enum重写了Object的readObject方法,当调用的时候会直接报异常
5. 枚举为什么可以避免clone问题?(重写了clone方法)
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {protected final Object clone() throws CloneNotSupportedException {// 原因在这里:直接报“不支持Clone”异常throw new CloneNotSupportedException();}
}
原因是:Enum重写了Object的clone方法,当调用的时候会直接报异常
2. 懒汉模式单例(在类加载时不初始化,调用获取单例的方法时候再初始化)
在调用静态方法getInstance时会实例化,
1. 在静态方法中使用双重校验(保证只创建一个实例)
public class SingletonDemo {private volatile static SingletonDemo instance;private SingletonDemo(){}public static SingletonDemo getInsatance(){// 第一次校验,如果已经创建好实例的话,就不用去获取锁了if (instance == null) {// A、B两个线程同时到这里来了,A获取了锁,B在这里阻塞等待synchronized (SingletonDemo.class) {// 第二次校验,防止未创建实例时,A获取锁创建了实例,B之后获取锁又创建了实例if (instance == null) {instance = new SingletonDemo();}}}return singletonDemo;}
}
注意:这里用到了双重校验机制,2个IF分别有什么作用要记住,一个都不能删掉
2. 在静态方法中调用静态内部类
public class SingletonDemo {// 静态内部类private static class SingletonHolder{private static final SingletonDemo instance = new SingletonDemo();}private SingletonDemo(){}public static final SingletonDemo getInsatance(){return SingletonHolder.instance;}
}
注意:静态内部类在类加载的时候,是不会被扫描JVM到的,所以不会在类加载的时候实例化
3. 基于容器实现单例(单例注册表)
Spring创建Bean的方式(有兴趣可以深入了解下)
spring的BeanDefinition(因为BeanDefinition)通过ConcurrentHashMap实现单例注册表的特殊方式实现单例模式
public class ContainerSingleton {private ContainerSingleton() {}// ioc容器本质就是一个ConcurrentHashMap(确认过了,是的)private static Map<String, Object> ioc = new ConcurrentHashMap<>();// 获取实例的方法,不同的是需要有入参public static Object getInstance(String className) {Object instance = null;// 第一次校验if (!ioc.containsKey(className)) {synchronized (ContainerSingleton.class) {// 第二次校验if (!ioc.containsKey(className)) {try {instance = Class.forName(className).newInstance();ioc.put(className, instance);} catch (Exception e) {e.printStackTrace();}return instance;}}}return ioc.get(className);}
}
本质是采用在静态方法中使用双重校验实现,区别是存放实例的地方变了
题外话:为什么BeanFactory和ApplicationContext都是调用BeanDefinition来初始化实例的,BeanDefinition使用懒加载的方式实现,BeanFactory可以理解,但是ApplicationContext是如何在Spring容器启动的时候去创建实例的?TODO
4. 非枚举创建的单例,存在的问题
原文链接:为什么要用枚举实现单例模式(避免反射、序列化问题)
1. 可以使用反射机制调用私有构造器,创建第二个实例
解决:修改构造方法,当调用的时候直接抛异常就行
2. 序列化前后,实例发生改变
public class SerSingleton implements Serializable {private volatile static SerSingleton uniqueInstance;private String content;public String getContent() {return content;}public void setContent(String content) {this.content = content;}private SerSingleton() {}public static SerSingleton getInstance() {if (uniqueInstance == null) {synchronized (SerSingleton.class) {if (uniqueInstance == null) {uniqueInstance = new SerSingleton();}}}return uniqueInstance;}public static void main(String[] args) throws IOException, ClassNotFoundException {SerSingleton s = SerSingleton.getInstance();s.setContent("单例序列化");System.out.println("序列化前读取其中的内容:"+s.getContent());ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerSingleton.obj"));oos.writeObject(s);oos.flush();oos.close();FileInputStream fis = new FileInputStream("SerSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);SerSingleton s1 = (SerSingleton)ois.readObject();ois.close();System.out.println(s+"\n"+s1);System.out.println("序列化后读取其中的内容:"+s1.getContent());System.out.println("序列化前后两个是否同一个:"+(s==s1));}
}
控制台:
序列化前读取其中的内容:单例序列化
com.lxp.pattern.singleton.SerSingleton@135fbaa4
com.lxp.pattern.singleton.SerSingleton@58372a00
序列化后读取其中的内容:单例序列化
序列化前后两个是否同一个:false
任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚举类型优于readResolve
因为readObject方法的存在,导致每次序列化前后的对象
解决:重写readObject方法并在方法内直接抛异常
3. 可以通过clone拷贝的方式去创建一个新的实例
解决:继承clone并在方法内直接抛异常
5. 单例模式常见面试题(TODO)
问题来源是在这里,答案是我自己总结的
原文链接:https://blog.csdn.net/androidzhaoxiaogang/article/details/6832364
1. 哪些类是单例模式的后续类?在Java中哪些类会成为单例?
这里它们将检查面试者是否有对使用单例模式有足够的使用经验。他是否熟悉单例模式的优点和缺点。
2. 你能在Java中编写单例里的getInstance()的代码?
使用双重校验或静态内部类
很多面试者都在这里失败。然而如果不能编写出这个代码,那么后续的很多问题都不能被提及。
3. 在getInstance()方法上同步有优势还是仅同步必要的块更优优势?你更喜欢哪个方式?
只锁创建实例的那块代码就行,大多数情况下都是获取已经创建的实例
这确实是一个非常好的问题,我几乎每次都会提该问题,用于检查面试者是否会考虑由于锁定带来的性能开销。因为锁定仅仅在创建实例时才有意义,然后其他时候实例仅仅是只读访问的,因此只同步必要的块的性能更优,并且是更好的选择。
4. 什么是单例模式的延迟加载或早期加载?你如何实现它?
问的就是对类加载和性能开销的理解(这个问题在【八股文】JVM篇的时候再讲)
这是和Java中类加载的载入和性能开销的理解的又一个非常好的问题。我面试过的大部分面试者对此并不熟悉,但是最好理解这个概念。
5. JDK中的单例模式的实例有哪些?(好问题)
1.JDK中的饿汉模式(JVM启动时实例化)
1. java.lang.Runtime类(JVM进程启动的,供所有线程使用)
Runtime类封装了Java运行时的环境。每一个java程序实际上都是启动了一个JVM进程,那么每个JVM进程都是对应这一个Runtime实例,此实例是由JVM为其实例化的。每个Java应用程序都有一个 Runtime类实例,使应用程序能够与其运行的环境相连接。
由于Java是单进程的,所以,在一个JVM中,Runtime的实例应该只有一个。所以应该使用单例来实现。
1. 源码如下:
package java.lang;public class Runtime {private static Runtime currentRuntime = new Runtime();public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}
Runtime是通过饿汉模式的new方式创建实例的(实现的方法有点老了)
2. 为什么Runtime类要用饿汉模式?
Runtime很重要(封装了Java运行时的环境),让JWM去实例化,总不可能让JAVA应用程序去实例化吧
2. JDK中的懒汉模式(GUI包下)(JVM启动时不初始化)
1. java.awt.Toolkit#getDefaultToolkit()
2. java.awt.GraphicsEnvironment#getLocalGraphicsEnvironment()
3. java.awt.Desktop#getDesktop()
这三个类都是在JDK的GUI包下的,并不常使用,所以为了节省资源,使用了懒汉模式
这是个完全开放的问题,如果你了解JDK中的单例类,请共享给我。
6. 单例模式的两次检查锁是什么?
说的就是DCL双重校验机制,在懒汉模式下的
7. 你如何阻止使用clone()方法创建单例实例的另一个实例?
单例类去实现Object中的clone方法并直接抛出异常就可以了
问:枚举类默认能防止clone创建实例吗?可以的,Enum类重写了clone方法
该类型问题有时候会通过如何破坏单例或什么时候Java中的单例模式不是单例来被问及。
8. 如何阻止通过使用反射来创建单例类的另一个实例?
在私有的构造方法中抛出异常
开放的问题。在我的理解中,从构造方法中抛出异常可能是一个选项。
9. 如何阻止通过使用序列化来创建单例类的另一个实例?
实现Object的readObject方法并抛出异常
又一个非常好的问题,这需要Java中的序列化知识并需要理解如何使用它来序列化单例类。该问题是开放问题。
10. Java中的单例模式什么时候是非单例?
问的其实就是什么情况会导致多个实例,如未加锁的并发访问、反射调用构造方法、通过序列化的readObject创建实例、通过Object的clone方法(浅拷贝或深拷贝)创建实例等等(没等等了好像就这么多吧)
【设计模式】单例模式是什么?如何实现单例模式?单例模式常见问题?相关推荐
- JavaScript 设计模式核⼼原理与应⽤实践之单例模式——Vuex的数据管理哲学
JavaScript 设计模式核⼼原理与应⽤实践之单例模式--Vuex的数据管理哲学 保证一个类仅有一个实例,并提供一个访问它的全局访问点,这样的模式就叫做单例模式. 单例模式的实现思路 思考这样一个 ...
- 《设计模式详解》创建型模式 - 单例模式
设计模式详解 4.1 单例模式 4.1.1 单例模式的结构 4.1.2 单例模式的实现 饿汉式 1:静态变量 饿汉式 2:静态代码块 懒汉式 1:线程不安全 懒汉式 2:线程安全 懒汉式 3:双重检查 ...
- android单例模式代码,设计模式(一):Android 源码中的单例模式
设计模式(一):Android 源码中的单例模式 2020-08-17 22:51 阅读数 57 <>什么是单例模式? 单例模式(Singleton) 是最常见也最简单的设计模式,它的目的 ...
- 「大冰撸设计模式」java 创建型模式之单例模式
WEB攻城狮大冰 2017-02-13 07:00 今天起,笔者开始撸java和javascript的设计模式,先从java开始写起,再从java的设计模式中提取思想和方法,应用到javascript ...
- 设计模式、原则、饿汉式单例模式、抽象工厂、代理模式、观察者模式、模板方法模式使用场景
设计模式 对各种面向对象方法的一种总结.前辈们遇到了好多设计问题,然后利用面向对象解决了.然后他们把解决方案汇总起来,形成了20多种设计模式.它可以有效的帮助我们利用面向对象,来提高代码的复用性. ...
- 单例模式应用场景_三、单例模式详解
4.单例模式详解 4.1.课程目标 1.掌握单例模式的应用场景. 2.掌握IDEA环境下的多线程调试方式. 3.掌握保证线程安全的单例模式策略. 4.掌握反射暴力攻击单例解决方案及原理分析. 5.序列 ...
- Android设计模式系列(3)--SDK源码之单例模式
单例模式,可以说是GOF的23种设计模式中最简单的一个. 这个模式相对于其他几个模式比较独立,它只负责控制自己的实例化数量单一(而不是考虑为用户产生什么样的实例),很有意思,是一个感觉上很干净的模式, ...
- 设计模式(一):Android 源码中的单例模式
什么是单例模式? 单例模式(Singleton) 是最常见也最简单的设计模式,它的目的就是在全局只生成一个类的实例. 什么场合用单例模式 应用中经常有多任务进行信息共享的需求,比如火车票售卖示例中,多 ...
- 23种设计模式C++源码与UML实现--单例模式中的饿汉模式和懒汉模式
单例模式 单例模式是一种对象创建模式,使用单例模式,可以保证为一个类生成唯一的实例对象.也就是说在这个程序空间该类只有一个实例对象. GoF对单例的定义:保证一个类.只有一个实例存在,同时提供对该实例 ...
- 设计模式C++学习笔记之三(Singleton单例模式)
单例模式看起来也蛮简单的,就是在系统中只允许产生这个类的一个实例,既然这么简单,就直接贴代码了.更详细的内容及说明可以参考原作者博客:cbf4life.cnblogs.com. 3.1.解释 main ...
最新文章
- mcs 4微型计算机,MCS-II高性能自主品牌的微机测速仪
- xshell怎么连接windows server_未能连接一个Windows服务 怎么回事
- Git之深入解析如何使用Git的分布式工作流程与如何管理多人开发贡献的项目
- 透明地持久保存并从数据库中检索加密的数据
- 47.leetcode36_valid_suduko
- sharepoint被阻止的文件类型解释说明
- _GUN_SOURCE宏
- xmlhttp的状态码收集
- 数据库封装 sql server mysql_sqlserver数据库操作封装
- 批处理创建快捷方式【简单好用详解】
- Packet Tracer安装包及安装教程(8.0版本)
- HTML调用Discuz系统变量,Discuz论坛js调用详解
- 7步轻松设置授权管理器License Manager
- 工件SSMwar exploded 部署工件时出错。请参阅服务器日志了解详细信息
- 开机hidl报错修改
- STL库:vector
- 你也可以找到好工作(三)大结局
- python下载图片插入excel_Python向Excel中插入图片的简单实现方法
- 【数值模型系列】link_grib.csh脚本解读
- 23.MongoDB地理位置检索
热门文章
- C++函数重载(6) - main函数重载
- Java Scanner用法详解
- LaTex常用公式和字母
- 工具 svn 介绍和简单用法
- paddle 40 支持任意维度数据的梯度平衡机制GHM Loss的实现(支持ignore_index、class_weight,支持反向传播训练,支持多分类)
- 函数式编程语言的入门级了解
- 为什么千万不要小瞧月薪几千的女生
- html table拓宽,excel拉长单元格_excel调整单元格大小的方法步骤详解
- 【实习周记】微信网络组件——腾讯Mars框架的原理、编译和使用
- SDI、PDI、UART,I2C,SPI 接口总结