延迟加载,只有在真正使用的时候,才开始实例化;
- 线程安全问题double check 枷锁优化编译器(JIT),CPU可能会对指令进行重重新排序,导致使用到尚未初始化的实力,可以通过添加volatile关键字机进行修饰,对于volatile修饰的字段,可以防止指令重排序以及内存立马可见;
推荐使用的代码示例:
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){ }
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
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;
}
}
2,饿汉模式:
类加载的初始化阶段完成了实例的初始化。本质上是借助JVM类加载机制,保证实例的唯一性。
类加载过程:
1,加载二进制数据到内存中,生成对应的Class数据结构;
2,连接:a:验证,b:准备(给类的成员变量赋默认值),c:解析
3,初始化:给类的金泰变量赋予初始值
只有在真正使用对应的类时,才会触发初始化(当前类是启动类即main函数所在类,直接进行new操作,访问静态属性,访问静态方法,用反射访问类,初始化一个类的子类等)
示例代码:
public class SingletonDemo {
//在类的内部,设置静态属性,并初始化该对象,这样在类的加载过程中,即可初始化该对象;
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
return instance;
}
}
源码中的应用:java.lang.Runtime
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
private static Version version;
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
3,静态内部类
本质上是利用类的加载机制保证线程安全;
只有在实际使用的时候,才会触发类的初始化,所以也是一种懒汉模式;因为静态内部类的加载不需要依附外部类,在使用时才加载。不过在加载静态内部类的过程中也会加载外部类。
代码示例:
public class Singleton {
private Singleton() {
}
private static class InsideSingleton {
private static final Singleton SINGLETON = new Singleton();
}
public static Singleton getInstance() {
return InsideSingleton.SINGLETON;
}
}
4,通过枚举实现单例模式
public enum SingletonEnumV2 {
INSTANCE;
private SingletonEnumV2() {
}
public void doSomething() {
System.out.println("doSomething");
}
}
编译Singleton类,并通过javap命令查看该类的字节码信息,虽然只有几行代码,但是jvm会编译的字节码如下:
javap -v -p SingletonEnumV2.class Classfile /D:/project/nhw-project/nhw-java/target/classes/design/SingletonEnumV2.class Last modified 2022年1月12日; size 1120 bytes SHA-256 checksum 26555b703aa815d5d3af15b2104e32070ae6e30d1372b372bf7ca89faa0602b1 Compiled from "SingletonEnumV2.java" //会生成一个final 类 SingletonEnumV2 并且会继承Enum,所以枚举实际也是java 类 public final class design.SingletonEnumV2 extends java.lang.Enumminor version: 0 major version: 52 flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM this_class: #2 // design/SingletonEnumV2 super_class: #13 // java/lang/Enum interfaces: 0, fields: 2, methods: 5, attributes: 2 //常量池: Constant pool: #1 = Fieldref #2.#3 // design/SingletonEnumV2.$VALUES:[Ldesign/SingletonEnumV2; #2 = Class #4 // design/SingletonEnumV2 #3 = NameAndType #5:#6 // $VALUES:[Ldesign/SingletonEnumV2; #4 = Utf8 design/SingletonEnumV2 #5 = Utf8 $VALUES #6 = Utf8 [Ldesign/SingletonEnumV2; #7 = Methodref #8.#9 // "[Ldesign/SingletonEnumV2;".clone:()Ljava/lang/Object; #8 = Class #6 // "[Ldesign/SingletonEnumV2;" #9 = NameAndType #10:#11 // clone:()Ljava/lang/Object; #10 = Utf8 clone #11 = Utf8 ()Ljava/lang/Object; #12 = Methodref #13.#14 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #13 = Class #15 // java/lang/Enum #14 = NameAndType #16:#17 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #15 = Utf8 java/lang/Enum #16 = Utf8 valueOf #17 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; #18 = Methodref #13.#19 // java/lang/Enum." ":(Ljava/lang/String;I)V #19 = NameAndType #20:#21 // " ":(Ljava/lang/String;I)V #20 = Utf8 #21 = Utf8 (Ljava/lang/String;I)V #22 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream; #23 = Class #25 // java/lang/System #24 = NameAndType #26:#27 // out:Ljava/io/PrintStream; #25 = Utf8 java/lang/System #26 = Utf8 out #27 = Utf8 Ljava/io/PrintStream; #28 = String #29 // doSomething #29 = Utf8 doSomething #30 = Methodref #31.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V #31 = Class #33 // java/io/PrintStream #32 = NameAndType #34:#35 // println:(Ljava/lang/String;)V #33 = Utf8 java/io/PrintStream #34 = Utf8 println #35 = Utf8 (Ljava/lang/String;)V #36 = String #37 // INSTANCE #37 = Utf8 INSTANCE #38 = Methodref #2.#19 // design/SingletonEnumV2." ":(Ljava/lang/String;I)V #39 = Fieldref #2.#40 // design/SingletonEnumV2.INSTANCE:Ldesign/SingletonEnumV2; #40 = NameAndType #37:#41 // INSTANCE:Ldesign/SingletonEnumV2; #41 = Utf8 Ldesign/SingletonEnumV2; #42 = Utf8 values #43 = Utf8 ()[Ldesign/SingletonEnumV2; #44 = Utf8 Code #45 = Utf8 LineNumberTable #46 = Utf8 (Ljava/lang/String;)Ldesign/SingletonEnumV2; #47 = Utf8 LocalVariableTable #48 = Utf8 name #49 = Utf8 Ljava/lang/String; #50 = Utf8 this #51 = Utf8 Signature #52 = Utf8 ()V #53 = Utf8 #54 = Utf8 Ljava/lang/Enum ; #55 = Utf8 SourceFile #56 = Utf8 SingletonEnumV2.java { //SingletonEnumV2 类的静态属性; public static final design.SingletonEnumV2 INSTANCE; descriptor: Ldesign/SingletonEnumV2; flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM private static final design.SingletonEnumV2[] $VALUES; descriptor: [Ldesign/SingletonEnumV2; flags: (0x101a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC public static design.SingletonEnumV2[] values(); descriptor: ()[Ldesign/SingletonEnumV2; flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[Ldesign/SingletonEnumV2; 3: invokevirtual #7 // Method "[Ldesign/SingletonEnumV2;".clone:()Ljava/lang/Object; 6: checkcast #8 // class "[Ldesign/SingletonEnumV2;" 9: areturn LineNumberTable: line 3: 0 //静态的valueOf方法 public static design.SingletonEnumV2 valueOf(java.lang.String); descriptor: (Ljava/lang/String;)Ldesign/SingletonEnumV2; flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #2 // class design/SingletonEnumV2 2: aload_0 3: invokestatic #12 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #2 // class design/SingletonEnumV2 9: areturn LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 10 0 name Ljava/lang/String; //一个私有的构造函数,并且接受一个 String类型的参数 和Int类型的 参数 private design.SingletonEnumV2(); descriptor: (Ljava/lang/String;I)V flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=3, args_size=3 0: aload_0 1: aload_1 2: iload_2 3: invokespecial #18 // Method java/lang/Enum." ":(Ljava/lang/String;I)V 6: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Ldesign/SingletonEnumV2; Signature: #52 // ()V //枚举类中定义的方法 public void doSomething(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #28 // String doSomething 5: invokevirtual #30 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 9: 0 line 10: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Ldesign/SingletonEnumV2; //静态代码块:执行对象的实例化过程 static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=4, locals=0, args_size=0 //new 一个SingletonEnumV2对象 0: new #2 // class design/SingletonEnumV2 //其作用就是复制之前分配的空间的引用,并压入栈顶 3: dup //将常量INSTANCE 加载到栈顶 4: ldc #36 // String INSTANCE 6: iconst_0 //执行构造方式 7: invokespecial #38 // Method " ":(Ljava/lang/String;I)V //给INSTANCE赋值:上一步通过构造方法创建的对象:SingletonEnumV2 10: putstatic #39 // Field INSTANCE:Ldesign/SingletonEnumV2; //iconst_1 //将一个常量加载到操作数栈 13: iconst_1 //将SingletonEnumV2对象写入array数据 14: anewarray #2 // class design/SingletonEnumV2 //其作用就是复制之前分配的空间的引用,并压入栈顶 17: dup 18: iconst_0 19: getstatic #39 // Field INSTANCE:Ldesign/SingletonEnumV2; //astore将此时的栈顶值弹出存入局部变量中去 22: aastore 23: putstatic #1 // Field $VALUES:[Ldesign/SingletonEnumV2; 26: return LineNumberTable: line 5: 0 line 3: 13 } Signature: #54 // Ljava/lang/Enum ; SourceFile: "SingletonEnumV2.java"
单例模式并不安全
《effective java》中只简单的提了几句话:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚举类型优于readResolve()方法;**
总结:如下图:



