【设计模式1】单例模式
【设计模式2】工厂模式
【设计模式3】原型模式
【设计模式4】建造者模式
1,懒汉模式:
延迟加载,只有在真正使用的时候,才开始实例化;
- 线程安全问题
- 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;
/**
* Return a shared default {@code ReactiveAdapterRegistry} instance,
* lazily building it once needed.
* <p><b>NOTE:</b> We highly recommend passing a long-lived, pre-configured
* {@code ReactiveAdapterRegistry} instance for customization purposes.
* This accessor is only meant as a fallback for code paths that want to
* fall back on a default instance if one isn't provided.
* @return the shared {@code ReactiveAdapterRegistry} instance
* @since 5.0.2
*/
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;
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class {@code Runtime} are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the {@code Runtime} object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
3,静态内部类
本质上是利用类的加载机制保证线程安全;
只有在实际使用的时候,才会触发类的初始化,所以也是一种懒汉模式;因为静态内部类的加载不需要依附外部类,在使用时才加载。不过在加载静态内部类的过程中也会加载外部类。
代码示例:
public class Singleton {
/**
* 1.构造私有化
*/
private Singleton() {
}
/**
* 2.构建静态内部类
*/
private static class InsideSingleton {
private static final Singleton SINGLETON = new Singleton();
}
/**
* 3.提供公共方法获取实例
* 调用该方法时才会加载静态内部类
* @return
*/
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.Enum<design.SingletonEnumV2>
minor 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."<init>":(Ljava/lang/String;I)V
#19 = NameAndType #20:#21 // "<init>":(Ljava/lang/String;I)V
#20 = Utf8 <init>
#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."<init>":(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 <clinit>
#54 = Utf8 Ljava/lang/Enum<Ldesign/SingletonEnumV2;>;
#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."<init>":(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 "<init>":(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<Ldesign/SingletonEnumV2;>;
SourceFile: "SingletonEnumV2.java"
单例模式并不安全
《effective java》中只简单的提了几句话:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚举类型优于readResolve()方法;**
总结:如下图: