单例模式

Meteor 2023-10-25 17:00:16
Categories: Tags:

简介

单例模式:一个类在任何时候只有一个实例

单例模式的好处很明显,只有一个实例,节约了空间,只需要创建一次,也节约了时间,缺点就是没有抽象类,无法扩展

源码:singleton

下面展示单例模式的六种实现方式,其中所有实现方式的构造方法都设置成private的,只能通过指定方法才能获取实例对象

饿汉式

饿汉式,将实例设置成静态变量,只要类加载就实例化,JVM在执行实例化指令时会自动保证线程安全,实现简单,但是浪费空间,同时可以利用反射破坏单例

SingletonHungry

package com.meteor.design;

public class SingletonHungry {
private SingletonHungry() {

}

private final static SingletonHungry instance = new SingletonHungry();

public static SingletonHungry getInstance() {
return instance;
}
}

测试

测试饿汉式单例,尝试用反射破坏单例

public class AppTest{
@Test
public void testSingletonHungry() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
SingletonHungry instance = SingletonHungry.getInstance();

Constructor<SingletonHungry> constructor = SingletonHungry.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingletonHungry singletonHungry = constructor.newInstance();

System.out.println(instance);
System.out.println(singletonHungry);
}
}

输出结果

com.meteor.design.SingletonHungry@2b05039f
com.meteor.design.SingletonHungry@61e717c2

Process finished with exit code 0

很明显,正常获取的单例和反射获取的单例不一致

饿汉式不安全

懒汉式不安全实现,当要使用的时候调用方法获取,如果为空就创建,将静态对象返回

SingletonLazyUnsafe

package com.meteor.design;

public class SingletonLazyUnsafe {
private SingletonLazyUnsafe() {

}

private static SingletonLazyUnsafe instance;

public static SingletonLazyUnsafe getInstance() {
if (instance == null) {
instance = new SingletonLazyUnsafe();
}
return instance;
}
}

测试

测试懒汉式不安全实现,用多线程破坏单例

public class AppTest {
@Test
public void testSingletonLazyUnsafe(){
for(int i = 0; i < 5; i++){
new Thread(()->{
System.out.println(SingletonLazyUnsafe.getInstance());
}).start();
}
}
}

输出结果

com.meteor.design.SingletonLazyUnsafe@2d7143c2
com.meteor.design.SingletonLazyUnsafe@5fb48d1e
com.meteor.design.SingletonLazyUnsafe@2d7143c2
com.meteor.design.SingletonLazyUnsafe@c087dd6
com.meteor.design.SingletonLazyUnsafe@2d7143c2

Process finished with exit code 0

不是每次都会出现问题,但肯定是要避免出问题的

懒汉式安全实现

使用 synchronized 关键字对方法加锁保证线程安全,但效率较低

SingletonLazySafe

package com.meteor.design;

public class SingletonLazySafe {
private SingletonLazySafe() {

}

private static SingletonLazySafe instance;

public synchronized static SingletonLazySafe getInstance() {
if (instance == null) {
instance = new SingletonLazySafe();
}
return instance;
}
}

内部类

使用静态内部类实现单例,静态内部类的特点是不依赖外部类的实例对象可以独立创建,调用getInstance()方法时才执行创建操作,实现了懒加载,而且JVM保证了执行实例化指令的线程安全

SingletonInnerClass

package com.meteor.design;

public class SingletonInnerClass {
private SingletonInnerClass() {

}

public static InnerClass getInstance() {
return InnerClass.INSTANCE;
}

public static class InnerClass {
private final static InnerClass INSTANCE = new InnerClass();
}
}

双重校验锁

双重校验锁,先判断是否为空,再进行加锁,再次判断是否为空,如果为空则实例化

第二次判断是防止第一次判断为空但是加锁之后已经实例化好了,同时要配合volatile使用,volatile的作用是防止指令重排序和变量的值与主存的值时刻保持一致,也就是为了加速代码执行效率可能会进行指令重排序,在单线程环境下不会影响结果,但是多线程环境下可能会拿到一个不为null,但还未初始化的对象,禁止重排序后,就只能先初始化,后赋值给对象,同时对对象的修改对其它线程可见

SingletonDoubleCheckedLocking

package com.meteor.design;

public class SingletonDoubleCheckedLocking {
private SingletonDoubleCheckedLocking() {

}

private static volatile SingletonDoubleCheckedLocking instance;

private static SingletonDoubleCheckedLocking getInstance() {
if (instance == null) {
synchronized (SingletonDoubleCheckedLocking.class) {
if (instance == null) {
instance = new SingletonDoubleCheckedLocking();
}
}
}
return instance;
}
}

枚举

利用枚举实现单例,线程安全且无法通过反射破坏单例,因为反射调用的方法检测到是枚举类时会报错

SingletonEnum

package com.meteor.design;

public enum SingletonEnum {
INSTANCE
}

目录结构

src
├── main
│ └── java
│ └── com.meteor.design
│ ├── dynamic_proxy
│ │ ├── cglib
│ │ │ ├── CGLIBProxyFactory.java
│ │ │ └── MyMethodInterceptor.java
│ │ │
│ │ └── jdk
│ │ ├── JDKProxyFactory.java
│ │ └── MyInvocationHandler.java
│ │
│ ├── service
│ │ ├── IUserService.java
│ │ └── UserService.java
│ │
│ └── static_proxy
│ └── StaticProxy.java

└── test
└── java
└── com.meteor.design
└── AppTest.java