单例模式问题清单 本是古典 何须时尚 2023-06-27 08:42 8阅读 0赞 ### 单例模式问题清单 ### * 什么是单例模式 * 什么是懒汉模式 * 饿汉模式 * 枚举实现单例 # 什么是单例模式 # 任何时候都只有一个实例,并提供全局访问点 # 什么是懒汉模式 # 使用的时候才开始实例化 1. 线程不安全的懒汉模式 public class LazySingleton{ public static LazySingleton singleton; /** * 提供全局访问点 * @return */ public static LazySingleton getInstance(){ if (singleton==null){ /** * 在多线程情况下,可能导致多个线程同时进入该if区,从而单例被破环 * 测试如下: * TimeUnit.MILLISECONDS.sleep(1200); */ singleton=new LazySingleton(); //编译器,cpu,JIT会对字节码进行重排序(2,3顺序可能会改变) //1.分配空间-----》返回一个指向该空间的内存引用 //2.对空间进行初始化 //3.把内存引用赋值给demo变量 } return singleton; } /** * 防止外界调用构造方法,破坏单例 */ private LazySingleton(){ } } 线程安全的双重检验懒汉模式【线程安全,防止指令重排,双重检查优化】 [双重检验锁为何非常高效,并且在多线程的单例中应用广泛][Link 1] public class LazySingleton { //public static LazySingleton singleton; //不加volatile会造成(jvm对它的)重排序,引发空指针问题 /** *如果Helper对象是一个不一变对象,即Helper对象的所有域(变量)都是final类型,那么即使没有使用 volatile ,双重检验锁也可以正常运行。这个思想主要是引用一个和int或者float一样的不可变对象(像String 或者 Integer),读写不可变对象的引用时都是原子的。 */ public volatile static LazySingleton singleton; /** * 提供全局访问点 * * @return */ //public static synchronized LazySingleton getInstance(){ //锁加在这里,太笨重了,性能低下 public static LazySingleton getInstance() { if (singleton == null) { //2个线程A,B过来了 synchronized (LazySingleton.class) { /** * synchronized + 双重检查机制 * 这是很高效的 * 如果synchronized 加在方法上,那么将在每一次执行getInstance()方法时使用同步锁。而双重检验锁的方式可以避免在创建helper对象后依然使用同步方法: // 错误的多线程版本 */ if (singleton == null) { /** *这一段代码并不能在使用优化过的编译器或者共享内存的多处理器的情况下正确执行。(译者注:我们使用的hotspot就有指令重排序等优化,指令重排序会影响双重检验锁正常运行) */ //如果没有if判断,A来new了一下,释放了锁,B又来new了一下,释放了锁 singleton = new LazySingleton(); //编译器,cpu,JIT会对字节码进行重排序(2,3顺序可能会改变, // 这样的话:来了一个C线程,开始第一个if的判断,发现singleton有引用了,【但该空间尚未初始化,就会导致null空指针】) //1.分配空间-----》返回一个指向该空间的内存引用 //2.对空间进行初始化 //3.把内存引用赋值给demo变量 } } } return singleton; } /** * 防止外界调用构造方法,破坏单例 */ private LazySingleton() { } } 其在spring中的应用如下: package org.springframework.core; public class ReactiveAdapterRegistry { @Nullable private static volatile ReactiveAdapterRegistry sharedInstance; public static ReactiveAdapterRegistry getSharedInstance() { ReactiveAdapterRegistry registry = sharedInstance; if (registry == null) { synchronized (ReactiveAdapterRegistry.class) { registry = sharedInstance; if (registry == null) { registry = new ReactiveAdapterRegistry(); sharedInstance = registry; } } } return registry; } # 饿汉模式 # 在类加载阶段就完成了实例的初始化 > 类加载 > 加载:—加载对应的二进制文件,并且在方法区创建对应的数据结构 > 连接:验证【验证字节码文件有无被篡改,是否合乎语法】,准备【引用类型赋值null,int赋值0.boolean赋值true】,解析【符号引用转直接引用】 > 初始化:给静态属性赋值 public class HungrySingeton { //类加载:class先创建,才能加载实例 private static HungrySingeton instance=new HungrySingeton(); //类加载加了锁的synchronized,多线程同时类加载,也只会有一个线程能够完成类加载 public static HungrySingeton getInstance(){ return instance; } private HungrySingeton(){ } } 延迟加载的饿汉模式 public class InnerClassSingleton { //这是一种懒加载 static class InnerClass { private static InnerClassSingleton instance = new InnerClassSingleton(); } public static InnerClassSingleton getInstance() { //InnerClassSingleton的类加载是导致InnerClass也类加载,此时会给静态属性赋初值。 //执行InnerClass.instance是会触发InnerClassSingleton的类加载 return InnerClass.instance; //使用内部类达到延迟类加载的效果。 } //私有的构造方法:不允许外部进行实例化 private InnerClassSingleton() { //使用异常来防止反射的方式破坏单例 if (InnerClass.instance!=null){ throw new RuntimeException("单例类,不允许多个实例"); } } } 反射对内部类实现的单例破坏如下: public class Anti { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<InnerClassSingleton>declaredConstructor=InnerClassSingleton.class.getDeclaredConstructor(); /** * 反射破坏 */ declaredConstructor.setAccessible(true); InnerClassSingleton innerClassSingleton=declaredConstructor.newInstance(); //反射获得的实例 InnerClassSingleton instance=innerClassSingleton.getInstance(); //通过静态方法获得的实例 System.out.println(innerClassSingleton==instance); //结果为false---说明反射破坏了单例 } } # 枚举实现单例 # 枚举类型其实是语法糖 public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { private final String name; //名称 private final int ordinal; //索引 protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } } final class EnumSingleton extends Enum<EnumSingleton>{ EnumSingleton INSTANCE; EnumSingleton[] $values; public static valueOf(); static{ new EnumSingleton (INSTACE,); //给静态属性赋值 //也是借助jvm类加载机制来保证单例 } } 枚举类型不支持反射来创建。 public 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); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); //枚举类型不支持反射来创建,枚举天然支持反射的攻击 ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } 最后枚举的单例的代码实现 public enum EnumSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } } 序列化实现饿汉模式 public class SerializableSingeton implements Serializable { // jvm会根据class的元素默认生成一个序列化id,所以最好加上 // private final long serialVersin=42L; private static SerializableSingeton instance = new SerializableSingeton(); //类加载加了锁的synchronized,多线程同时类加载,也只会有一个线程能够完成类加载 public static SerializableSingeton getInstance() { return instance; } String name; //最好指定序列化id,提高兼容性,像这里不加的话,加上了name属性,反序列化后就会报错 private SerializableSingeton() { } Object readResolve() throws ObjectStreamException{ return instance; //反序列化的时候就不会从流中读取,--指定签名,它会直接从 private static SerializableSingeton instance = new SerializableSingeton(); 中拿 } } public class Demo { public static void main(String[] args) throws IOException, ClassNotFoundException { //Demo demo=new Demo(); //new的后面回去查看是否完成了Demo.class文件的加载,初始化。如果没完成,就需要去完成 //在实例化的时候需要先完成类的加载。类加载完成后才会对实例进行初始化 SerializableSingeton instance=SerializableSingeton.getInstance(); /** * 序列化 */ ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("instance")); objectOutputStream.writeObject(instance); objectOutputStream.close(); /** * 反序列化 */ ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("instance")); Object object=objectInputStream.readObject(); SerializableSingeton singeton= (SerializableSingeton) object; System.out.println(singeton==instance); //返回false,序列化也可能会破坏单例。 //从指定地方获取实例,而非从流中获取实例。就是保证签名的一致性 } } ObjectInputStream里面的readObject方法: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDYzMTQx_size_16_color_FFFFFF_t_70] javap反汇编工具 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDYzMTQx_size_16_color_FFFFFF_t_70 1] javap -v -p Demo.class public class Demo { public static void main(String[] args) { Demo demo=new Demo(); //1.分配空间-----》指向该空间的内存引用 //2.在操作数栈上复制一个引用在下一个指令上面 //3.指令会消耗一个对应的引用,初始化 //4.把内存引用赋值给demo } } Classfile /E:/WORK_SPACE/Idea_workspace/blog_program_record/target/classes/org/lmj/singleton/Demo.class Last modified 2020-1-5; size 425 bytes MD5 checksum c6dd301a55168daa29e599d7ad28b5a2 Compiled from "Demo.java" public class org.lmj.singleton.Demo minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#19 // java/lang/Object."<init>":()V #2 = Class #20 // org/lmj/singleton/Demo #3 = Methodref #2.#19 // org/lmj/singleton/Demo."<init>":()V #4 = Class #21 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Utf8 LineNumberTable #9 = Utf8 LocalVariableTable #10 = Utf8 this #11 = Utf8 Lorg/lmj/singleton/Demo; #12 = Utf8 main #13 = Utf8 ([Ljava/lang/String;)V #14 = Utf8 args #15 = Utf8 [Ljava/lang/String; #16 = Utf8 demo #17 = Utf8 SourceFile #18 = Utf8 Demo.java #19 = NameAndType #5:#6 // "<init>":()V #20 = Utf8 org/lmj/singleton/Demo #21 = Utf8 java/lang/Object { public org.lmj.singleton.Demo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lorg/lmj/singleton/Demo; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class org/lmj/singleton/Demo 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: return LineNumberTable: line 5: 0 line 6: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; 8 1 1 demo Lorg/lmj/singleton/Demo; } SourceFile: "Demo.java" [Link 1]: https://blog.csdn.net/zhanlanmg/article/details/49944991 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDYzMTQx_size_16_color_FFFFFF_t_70]: https://img-blog.csdnimg.cn/20200105174558832.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDYzMTQx,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDYzMTQx_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/20200105152324906.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDYzMTQx,size_16,color_FFFFFF,t_70
相关 单例模式问题清单 单例模式问题清单 什么是单例模式 什么是懒汉模式 饿汉模式 枚举实现单例 什么是单例模式 任何时候都只有一个实例,并提供全局访问点 什 本是古典 何须时尚/ 2023年06月27日 08:42/ 0 赞/ 9 阅读
相关 单例模式 http://blog.csdn.net/zhengzhb/article/details/7331369 定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实 曾经终败给现在/ 2022年09月25日 15:30/ 0 赞/ 246 阅读
相关 单例模式 class sigle{ protected static $ins = null; public function getIns(){ 深藏阁楼爱情的钟/ 2022年07月20日 20:27/ 0 赞/ 266 阅读
相关 单例模式 简介: 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在 ﹏ヽ暗。殇╰゛Y/ 2022年05月09日 15:46/ 0 赞/ 160 阅读
相关 单例模式 <table> <tbody> <tr> <td style="vertical-align:top;width:.6868in;"> <p style 矫情吗;*/ 2021年11月22日 10:52/ 0 赞/ 298 阅读
相关 单例模式 单例模式 单例模式(SingletonPattern)是java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式涉及 红太狼/ 2021年11月16日 05:30/ 0 赞/ 315 阅读
相关 单例模式 应用场景 1. windows的任务管理器 2. 网站的计数器 3. 应用程序的日志 4. 数据库连接池,因为数据库连接是一种数据库资源。数据库软件系统中使用数据 r囧r小猫/ 2021年11月11日 15:08/ 0 赞/ 358 阅读
相关 单例模式 单例模式有以下特征: 1. 只有一个对象存在 2. 对象的实例化必须在类中实现 一、懒汉模式(线程不安全) package com.kevin; 谁借莪1个温暖的怀抱¢/ 2021年10月01日 07:48/ 0 赞/ 322 阅读
相关 单例模式 1.定义 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。 2.实现步骤 1. 将该类的构造方法定义为私有方法,这样其他处 小咪咪/ 2021年09月27日 13:56/ 0 赞/ 405 阅读
相关 单例模式 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责 Dear 丶/ 2021年09月17日 02:10/ 0 赞/ 335 阅读
还没有评论,来说两句吧...