什么是单例模式?

保证一个了类仅有一个实例,并提供一个访问它的全局访问点。

单例模式的应用场景?

  1. 网站的计数器,一般也是采用单例模式实现,否则难以同步;
  2. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源;
  3. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源;
  4. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

单例的优缺点?

优点:

  • 提供了对唯一实例的受控访问;
  • 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能;
  • 避免对共享资源的多重占用

缺点:

  • 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态;
  • 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难;
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。

单例的创建方式

饿汉式

类初始化时,会立即加载该对象,线程安全,效率高。

  1. /**
  2. * @Author 刘翊扬
  3. * @Version 1.0
  4. */
  5. public class SingletonHungry {
  6. private static SingletonHungry instance = new SingletonHungry();
  7. private SingletonHungry() {}
  8. public static SingletonHungry getInstance() {
  9. return instance;
  10. }
  11. }

验证:

  1. public class Main {
  2. public static void main(String[] args) {
  3. SingletonHungry instance1 = SingletonHungry.getInstance();
  4. SingletonHungry instance2 = SingletonHungry.getInstance();
  5. System.out.println(instance1 == instance2); // 结果是true
  6. }
  7. }

优点:仅实例化一次,线程是安全的。获取实例的速度快

缺点:类加载的时候立即实例化对象,可能实例化的对象不会被使用,造成内存的浪费。

使用静态代码块

  1. /**
  2. * @author 刘翊扬
  3. */
  4. public class HungrySingleton2 {
  5. private static HungrySingleton2 instance = null;
  6. private HungrySingleton2() {}
  7. static {
  8. instance = new HungrySingleton2();
  9. }
  10. private HungrySingleton2() {}
  11. public static HungrySingleton2 getInstance() {
  12. return instance;
  13. }
  14. }

懒汉式

  1. public class SingletonLazy {
  2. private static SingletonLazy instance;
  3. private SingletonLazy() {}
  4. public static SingletonLazy getInstance() {
  5. if (instance == null) {
  6. instance = new SingletonLazy();
  7. }
  8. return instance;
  9. }
  10. }

优点:在使用的时候,创建对象,节省系统资源

缺点:

  1. 如果获取实例时,初始化的工作量较多,加载速度会变慢,影响系统系能
  2. 每次获取对象,都要进行非空检查,系统开销大
  3. 非线程安全,当有多个线程同时调用 getInstance()方法,时,会有线程安全问题,可能导致创建多个对象

静态内部类

  1. /**
  2. * @Author 刘翊扬
  3. * @Version 1.0
  4. */
  5. public class SingletonDemo03 {
  6. private SingletonDemo03() {}
  7. public static class SingletonClassInstance {
  8. private static final SingletonDemo03 instance = new SingletonDemo03();
  9. }
  10. public static SingletonDemo03 getInstance() {
  11. return SingletonClassInstance.instance;
  12. }
  13. }

优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。

劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。

使用枚举

枚举本身就是单例的,一般在项目中定义常量。

例如:

  1. /**
  2. * @Author 刘翊扬
  3. * @Version 1.0
  4. */
  5. public enum ResultCode {
  6. SUCCESS(200, "SUCCESS"),
  7. ERROR(500, "ERROR");
  8. private Integer code;
  9. private String msg;
  10. ResultCode(Integer code, String msg) {
  11. this.code = code;
  12. this.msg = msg;
  13. }
  14. public Integer getCode() {
  15. return code;
  16. }
  17. public void setCode(Integer code) {
  18. this.code = code;
  19. }
  20. public String getMsg() {
  21. return msg;
  22. }
  23. public void setMsg(String msg) {
  24. this.msg = msg;
  25. }
  26. }
  1. /**
  2. * @Author 刘翊扬
  3. * @Version 1.0
  4. */
  5. public class User {
  6. private User() {}
  7. public static User getInstance() {
  8. return SingletonDemo04.INSTANCE.getInstance();
  9. }
  10. private static enum SingletonDemo04 {
  11. INSTANCE;
  12. // 枚举元素为单例
  13. private User user;
  14. SingletonDemo04() {
  15. user = new User();
  16. }
  17. public User getInstance() {
  18. return user;
  19. }
  20. }
  21. }

解决线程安全问题

使用双重检测锁

  1. public class LazySingletonDemo1 {
  2. private static LazySingletonDemo1 instance = null;
  3. public static LazySingletonDemo1 getInstance() {
  4. if (instance == null) {
  5. synchronized (LazySingletonDemo1.class) {
  6. if (instance == null) {
  7. instance = new LazySingletonDemo1();
  8. }
  9. }
  10. }
  11. return instance;
  12. }
  13. }

这里使用双重检测,是为了防止,当实例存在的时候,不在走同步锁,减少使用锁带来的性能的消耗。

单例模式一定能保证只有一个实例对象吗?

答案是:不能

破坏单例的两种方式:

  1. 反射
  2. 反序列化

反射破坏

通过反射是可以破坏单例的,例如使用内部类实现的单例。通过反射获取其默认的构造函数,然后使默认构造函数可访问,就可以创建新的对象了。

  1. /**
  2. * @Author 刘翊扬
  3. * @Version 1.0
  4. */
  5. public class ReflectionDestroySingleton {
  6. public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
  7. SingletonLazy instance = SingletonLazy.getInstance();
  8. Class aClass = SingletonLazy.class;
  9. // 获取默认的构造方法
  10. Constructor<SingletonLazy> declaredConstructor = aClass.getDeclaredConstructor();
  11. // 使默认构造方法可访问
  12. declaredConstructor.setAccessible(true);
  13. // 创建对象
  14. SingletonLazy instance2 = declaredConstructor.newInstance();
  15. System.out.println(instance == instance2); // 结果是:false
  16. }
  17. }

怎么阻止???

可以增加一个标志位,用来判断构造函数是否被调用了。

  1. public class SingletonLazy {
  2. // 标志位
  3. private static Boolean isNew = false;
  4. private static SingletonLazy instance;
  5. private SingletonLazy() {
  6. synchronized (SingletonLazy.class) {
  7. if (!isNew) {
  8. isNew = true;
  9. } else {
  10. throw new RuntimeException("单例模式被破坏!");
  11. }
  12. }
  13. }
  14. public static SingletonLazy getInstance() {
  15. if (instance == null) {
  16. instance = new SingletonLazy();
  17. }
  18. return instance;
  19. }
  20. }

再次运行:

注意:

增加标志位的确能阻止单例的破坏,但是这个代码有一个BUG,那就是如果单例是先用的反射创建的,那如果你再用正常的方法getInstance()获取单例,就会报错。因为此时标志位已经标志构造函数被调用过了。这种写法除非你能保证getInstance先于反射执行。

  1. public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
  2. Class aClass = SingletonLazy.class;
  3. // 获取默认的构造方法
  4. Constructor<SingletonLazy> declaredConstructor = aClass.getDeclaredConstructor();
  5. // 使默认构造方法可访问
  6. declaredConstructor.setAccessible(true);
  7. // 创建对象
  8. SingletonLazy instance2 = declaredConstructor.newInstance();
  9. System.out.println("反射实例:" + instance2);
  10. // 再次调用
  11. SingletonLazy instance = SingletonLazy.getInstance();
  12. System.out.println(instance == instance2); // 结果是:false
  13. }

结果:

反序列化

SingletonLazy要实现Serializable接口

  1. public static void main(String[] args) throws Exception {
  2. //序列化
  3. SingletonLazy instance1 = SingletonLazy.getInstance();
  4. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempfile.txt"));
  5. out.writeObject(SingletonLazy.getInstance());
  6. File file = new File("tempfile.txt");
  7. ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
  8. //调用readObject()反序列化
  9. SingletonLazy instance2 = (SingletonLazy) in.readObject();
  10. System.out.println(instance1 == instance2); // 结果是:false
  11. }

原理解释:

反序列化为什么能生成新的实例,必须从源码看起。这里分析readObject()里面的调用源码。会发现readObject()方法后进入了readObject0(false)方法。

  1. public final Object readObject()
  2. throws IOException, ClassNotFoundException
  3. {
  4. if (enableOverride) {
  5. return readObjectOverride();
  6. }
  7. // if nested read, passHandle contains handle of enclosing object
  8. int outerHandle = passHandle;
  9. try {
  10. Object obj = readObject0(false); //通过debug会发现进入此方法
  11. handles.markDependency(outerHandle, passHandle);
  12. ClassNotFoundException ex = handles.lookupException(passHandle);
  13. if (ex != null) {
  14. throw ex;
  15. }
  16. if (depth == 0) {
  17. vlist.doCallbacks();
  18. }
  19. return obj;
  20. } finally {
  21. passHandle = outerHandle;
  22. if (closed && depth == 0) {
  23. clear();
  24. }
  25. }
  26. }

分析readObject0方法,通过debug进入了readOrdinaryObject()方法。

  1. private Object readObject0(Class<?> type, boolean unshared) throws IOException {
  2. boolean oldMode = bin.getBlockDataMode();
  3. if (oldMode) {
  4. int remain = bin.currentBlockRemaining();
  5. if (remain > 0) {
  6. throw new OptionalDataException(remain);
  7. } else if (defaultDataEnd) {
  8. /*
  9. * Fix for 4360508: stream is currently at the end of a field
  10. * value block written via default serialization; since there
  11. * is no terminating TC_ENDBLOCKDATA tag, simulate
  12. * end-of-custom-data behavior explicitly.
  13. */
  14. throw new OptionalDataException(true);
  15. }
  16. bin.setBlockDataMode(false);
  17. }
  18. byte tc;
  19. while ((tc = bin.peekByte()) == TC_RESET) {
  20. bin.readByte();
  21. handleReset();
  22. }
  23. depth++;
  24. totalObjectRefs++;
  25. try {
  26. switch (tc) {
  27. .... 省略部分源码
  28. case TC_OBJECT:
  29. if (type == String.class) {
  30. throw new ClassCastException("Cannot cast an object to java.lang.String");
  31. }
  32. return checkResolve(readOrdinaryObject(unshared)); // 通过debug发现进入到了readOrdinaryObject()方法。
  33. .... 省略部分源码
  34. }
  35. } finally {
  36. depth--;
  37. bin.setBlockDataMode(oldMode);
  38. }
  39. }

通过分析,readOrdinaryObject()中有两处关键代码,其中关键代码1中的关键语句为:

此处代码是通过描述对象desc,先判断类是否可以实例化,如果可以实例化,则执行desc.newInstance()通过反射实例化类,否则返回null。

obj = desc.isInstantiable() ? desc.newInstance() : null;

  1. private Object readOrdinaryObject(boolean unshared)
  2. throws IOException
  3. {
  4. if (bin.readByte() != TC_OBJECT) {
  5. throw new InternalError();
  6. }
  7. ObjectStreamClass desc = readClassDesc(false);
  8. desc.checkDeserialize();
  9. Class<?> cl = desc.forClass();
  10. if (cl == String.class || cl == Class.class
  11. || cl == ObjectStreamClass.class) {
  12. throw new InvalidClassException("invalid class descriptor");
  13. }
  14. Object obj;
  15. try {
  16. // 关键代码=========
  17. obj = desc.isInstantiable() ? desc.newInstance() : null;
  18. } catch (Exception ex) {
  19. throw (IOException) new InvalidClassException(
  20. desc.forClass().getName(),
  21. "unable to create instance").initCause(ex);
  22. }
  23. passHandle = handles.assign(unshared ? unsharedMarker : obj);
  24. ClassNotFoundException resolveEx = desc.getResolveException();
  25. if (resolveEx != null) {
  26. handles.markException(passHandle, resolveEx);
  27. }
  28. if (desc.isExternalizable()) {
  29. readExternalData((Externalizable) obj, desc);
  30. } else {
  31. readSerialData(obj, desc);
  32. }
  33. handles.finish(passHandle);
  34. if (obj != null &&
  35. handles.lookupException(passHandle) == null &&
  36. desc.hasReadResolveMethod())
  37. {
  38. Object rep = desc.invokeReadResolve(obj);
  39. if (unshared && rep.getClass().isArray()) {
  40. rep = cloneArray(rep);
  41. }
  42. if (rep != obj) {
  43. // Filter the replacement object
  44. if (rep != null) {
  45. if (rep.getClass().isArray()) {
  46. filterCheck(rep.getClass(), Array.getLength(rep));
  47. } else {
  48. filterCheck(rep.getClass(), -1);
  49. }
  50. }
  51. handles.setObject(passHandle, obj = rep);
  52. }
  53. }
  54. return obj;
  55. }

通过断点调试,发现其调用了desc.newInstance()方法。

我们知道调用newInstance()方法,一定会走类的无参构造方法,但是上面通过debug我们发现,cons的类型是Object类型,所以,这里面应该是调用类Object的无参构造方法,而不是SingletonLazy类的无参构造

那么怎么改造呢????

继续debug调试:查看readOrdinaryObject()方法

发现,desc.hasReadResolveMethod()这个方法返回的false,所以导致没有执行if条件下面的语句。

desc.hasReadResolveMethod() // 从方法名可以看到,这个方法的名字是检查desc这个(SingleLazy)对象有没有readResolve()方法。

我们现在阻止破坏单例,应该只需要在SingleLazy类中,实现自己的readResolve()方法即可。

  1. public Object readResolve() {
  2. return instance;
  3. }

现在我们在看看运行的结果:为true

大功告成。。。。

java设计模式—单例模式(包含单例的破坏)的更多相关文章

  1. Java设计模式:Singleton(单例)模式

    概念定义 Singleton(单例)模式是指在程序运行期间, 某些类只实例化一次,创建一个全局唯一对象.因此,单例类只能有一个实例,且必须自己创建自己的这个唯一实例,并对外提供访问该实例的方式. 单例 ...

  2. 【java设计模式】之 单例(Singleton)模式

    1. 单例模式的定义 单例模式(Singleton Pattern)是一个比較简单的模式.其原始定义例如以下:Ensure a class has only one instance, and pro ...

  3. Java面试 - 什么是单例设计模式,为什么要使用单例设计模式,如何实现单例设计模式(饿汉式和懒汉式)?

    什么是单例设计模式? 单例设计模式就是一种控制实例化对象个数的设计模式. 为什么要使用单例设计模式? 使用单例设计模式可以节省内存空间,提高性能.因为很多情况下,有些类是不需要重复产生对象的. 如果重 ...

  4. Java设计模式 - - 单例模式 装饰者模式

    Java设计模式 单例模式 装饰者模式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 静态代理模式:https://www.cnblogs.com/StanleyBlogs/p/1 ...

  5. Java设计模式-单例模式(Singleton)

    单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处: 1.某些类创建比较频繁,对于一些大型的对象,这是一笔 ...

  6. Java设计模式の单例模式

    -------------------------------------------------- 目录 1.定义 2.常见的集中单例实现 a.饿汉式,线程安全 但效率比较低 b.单例模式的实现:饱 ...

  7. java设计模式 - 单例模式(干货)

    深度讲解23种设计模式,力争每种设计模式都刨析到底.废话不多说,开始第一种设计模式 - 单例. 作者已知的单例模式有8种写法,而每一种写法,都有自身的优缺点. 1,使用频率最高的写法,废话不多说,直接 ...

  8. java设计模式单例模式 ----懒汉式与饿汉式的区别

    常用的五种单例模式实现方式 ——主要: 1.饿汉式(线程安全,调用率高,但是,不能延迟加载.) 2.懒汉式(线程安全,调用效率不高,可以延时加载.) ——其他: 1.双重检测锁式(由于JVM底层内部模 ...

  9. JAVA设计模式-单例模式(Singleton)线程安全与效率

    一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...

随机推荐

  1. HashSet 和 HashMap 的比较

    HashMap 和 HashSet 都是 collection 框架的一部分,它们让我们能够使用对象的集合.collection 框架有自己的接口和实现,主要分为 Set 接口,List 接口和 Qu ...

  2. JMeter Gui – TestElement约定[转]

    转自https://www.cnblogs.com/yigui/p/7615635.html 在编写任何JMeter组件时,必须注意某些特定的约定--如果JMeter环境中正确地运行JMeter组件, ...

  3. Lazysysadmin靶机

    仅供个人娱乐 靶机信息 Lazysysadmin靶机百度云下载链接:https://pan.baidu.com/s/1pTg38wf3oWQlKNUaT-s7qQ提取码:q6zo 信息收集 nmap全 ...

  4. 破解网站码验证,Java实现,不调用任何平台api接口

    package image.images; import java.io.File; import java.io.IOException; import java.io.InputStream; i ...

  5. OpenGL学习笔记(五)变换

    目录 变换 向量 向量的运算 向量与标量运算 向量取反 向量加减 求向量长度 向量的单位化 向量相乘 点乘(Dot Product) 叉乘 矩阵 矩阵的加减 矩阵的数乘 矩阵相乘 矩阵与向量相乘 与单 ...

  6. C 购买商品的游戏

    1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 /* 5 *模拟实现道具店购物 ...

  7. 05.表达式目录树Expression

    参考文章 https://www.cnblogs.com/xyh9039/p/12748983.html 1. 基本了解 1.1 Lambda表达式 演变过程 using System; namesp ...

  8. 玩转Java8日期工具类-基础

    内容基于的是 Java8官方文档,以及Java时间类总结 的总结.BTW:其实具体方法的使用直接在IDEA中看源码更方便直接. 1.老一辈:Java.util.Date Java.sql.Date J ...

  9. Python语言系列-02-基础数据类型

    格式化输出 #!/usr/bin/env python3 # author:Alnk(李成果) # 百分号% 格式化输出 name = input('姓名:') age = input('年龄:') ...

  10. Let's Encrypt泛域名使用和Nginx配置拆分

    上一期写了 使用Let's Encrypt实现网站https化 ,随着二级域名的增多,每个二级域名需要一张 SSL 证书,这可太不优雅了,泛域名表示我可以更优雅. 作者:IT王小二 博客:https: ...