目录

  • 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. 私有静态实例引用
  2. 私有的构造方法
  3. 返回静态实例的静态公有方法

白话文:1个构造方法 + 1个引用(不一定有实例)+ 1个获取单例的方法

2. 优点

  1. 在内存中只有一个对象,节省内存空间;
  2. 避免频繁的创建销毁对象,可以提高性能;(只有一个实例)
  3. 避免对共享资源的多重占用,简化访问;
  4. 为整个系统提供一个全局访问点。(提供了获取单例的方法)

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方法(浅拷贝或深拷贝)创建实例等等(没等等了好像就这么多吧)

【设计模式】单例模式是什么?如何实现单例模式?单例模式常见问题?相关推荐

  1. JavaScript 设计模式核⼼原理与应⽤实践之单例模式——Vuex的数据管理哲学

    JavaScript 设计模式核⼼原理与应⽤实践之单例模式--Vuex的数据管理哲学 保证一个类仅有一个实例,并提供一个访问它的全局访问点,这样的模式就叫做单例模式. 单例模式的实现思路 思考这样一个 ...

  2. 《设计模式详解》创建型模式 - 单例模式

    设计模式详解 4.1 单例模式 4.1.1 单例模式的结构 4.1.2 单例模式的实现 饿汉式 1:静态变量 饿汉式 2:静态代码块 懒汉式 1:线程不安全 懒汉式 2:线程安全 懒汉式 3:双重检查 ...

  3. android单例模式代码,设计模式(一):Android 源码中的单例模式

    设计模式(一):Android 源码中的单例模式 2020-08-17 22:51 阅读数 57 <>什么是单例模式? 单例模式(Singleton) 是最常见也最简单的设计模式,它的目的 ...

  4. 「大冰撸设计模式」java 创建型模式之单例模式

    WEB攻城狮大冰 2017-02-13 07:00 今天起,笔者开始撸java和javascript的设计模式,先从java开始写起,再从java的设计模式中提取思想和方法,应用到javascript ...

  5. 设计模式、原则、饿汉式单例模式、抽象工厂、代理模式、观察者模式、模板方法模式使用场景

    设计模式 ​ 对各种面向对象方法的一种总结.前辈们遇到了好多设计问题,然后利用面向对象解决了.然后他们把解决方案汇总起来,形成了20多种设计模式.它可以有效的帮助我们利用面向对象,来提高代码的复用性. ...

  6. 单例模式应用场景_三、单例模式详解

    4.单例模式详解 4.1.课程目标 1.掌握单例模式的应用场景. 2.掌握IDEA环境下的多线程调试方式. 3.掌握保证线程安全的单例模式策略. 4.掌握反射暴力攻击单例解决方案及原理分析. 5.序列 ...

  7. Android设计模式系列(3)--SDK源码之单例模式

    单例模式,可以说是GOF的23种设计模式中最简单的一个. 这个模式相对于其他几个模式比较独立,它只负责控制自己的实例化数量单一(而不是考虑为用户产生什么样的实例),很有意思,是一个感觉上很干净的模式, ...

  8. 设计模式(一):Android 源码中的单例模式

    什么是单例模式? 单例模式(Singleton) 是最常见也最简单的设计模式,它的目的就是在全局只生成一个类的实例. 什么场合用单例模式 应用中经常有多任务进行信息共享的需求,比如火车票售卖示例中,多 ...

  9. 23种设计模式C++源码与UML实现--单例模式中的饿汉模式和懒汉模式

    单例模式 单例模式是一种对象创建模式,使用单例模式,可以保证为一个类生成唯一的实例对象.也就是说在这个程序空间该类只有一个实例对象. GoF对单例的定义:保证一个类.只有一个实例存在,同时提供对该实例 ...

  10. 设计模式C++学习笔记之三(Singleton单例模式)

    单例模式看起来也蛮简单的,就是在系统中只允许产生这个类的一个实例,既然这么简单,就直接贴代码了.更详细的内容及说明可以参考原作者博客:cbf4life.cnblogs.com. 3.1.解释 main ...

最新文章

  1. mcs 4微型计算机,MCS-II高性能自主品牌的微机测速仪
  2. xshell怎么连接windows server_未能连接一个Windows服务 怎么回事
  3. Git之深入解析如何使用Git的分布式工作流程与如何管理多人开发贡献的项目
  4. 透明地持久保存并从数据库中检索加密的数据
  5. 47.leetcode36_valid_suduko
  6. sharepoint被阻止的文件类型解释说明
  7. _GUN_SOURCE宏
  8. xmlhttp的状态码收集
  9. 数据库封装 sql server mysql_sqlserver数据库操作封装
  10. 批处理创建快捷方式【简单好用详解】
  11. Packet Tracer安装包及安装教程(8.0版本)
  12. HTML调用Discuz系统变量,Discuz论坛js调用详解
  13. 7步轻松设置授权管理器License Manager
  14. 工件SSMwar exploded 部署工件时出错。请参阅服务器日志了解详细信息
  15. 开机hidl报错修改
  16. STL库:vector
  17. 你也可以找到好工作(三)大结局
  18. python下载图片插入excel_Python向Excel中插入图片的简单实现方法
  19. 【数值模型系列】link_grib.csh脚本解读
  20. 23.MongoDB地理位置检索

热门文章

  1. C++函数重载(6) - main函数重载
  2. Java Scanner用法详解
  3. LaTex常用公式和字母
  4. 工具 svn 介绍和简单用法
  5. paddle 40 支持任意维度数据的梯度平衡机制GHM Loss的实现(支持ignore_index、class_weight,支持反向传播训练,支持多分类)
  6. 函数式编程语言的入门级了解
  7. 为什么千万不要小瞧月薪几千的女生
  8. html table拓宽,excel拉长单元格_excel调整单元格大小的方法步骤详解
  9. 【实习周记】微信网络组件——腾讯Mars框架的原理、编译和使用
  10. SDI、PDI、UART,I2C,SPI 接口总结