在上一篇文章《设计模式(1):单例模式(上)》中讲解了单例模式的定义以及四种写法,最后着重讲解了懒汉式单例模式的线程安全性问题,但是没有讲解单例模式在实际coding的时候怎么用的,这篇文章将会讲解四种单例模式的应用案例。
1、饿汉式
饿汉式单例模式在实际工作中比较少见,但是在java核心类库中有个类使用了很经典的饿汉式单例写法:java.lang.Runtime
类
java.lang.Runtime
类是 Java 核心库提供的一个与运行时环境进行交互的类。它允许 Java 应用程序与操作系统进行通信、执行系统级操作,并提供了一些管理应用程序运行时的方法。
为什么Runtime类要设计成单例模式?
我的理解是Runtime类是java程序和操作系统交互的“代言人”,代言人没有必要有多个,只有一个的话办事效率更高,比如全局的管理资源和方法,如垃圾回收、内存信息等。通过单例模式,可以确保在整个应用程序中共享这些全局资源的状态和操作。
此外,Unsafe类、Sl4j中的StaticLoggerBinder类都使用了饿汉式单例模式。
2、懒汉式
这个说下我工作中实际遇到的问题的解决方案吧,我遇到的问题是Jackson组件ObjectMapper的使用问题,ObjectMapper每次创建都要new一下,后来我查了资料,发现它是线程安全的,如果一直new,效率会比较低(详细见文章《ObjectMapper,别再像个二货一样一直new了!》),全局用一个ObjectMapper对象就行,所以我写了个JsonUtils类
@Slf4j
public class JsonUtils {
private static volatile ObjectMapper objectMapper;
private JsonUtils() {
// 私有构造函数的逻辑
}
//获取ObjectMapper单例对象
public static ObjectMapper getObjectMapper() {
if (Objects.nonNull(objectMapper)) {
return objectMapper;
}
synchronized (JsonUtils.class) {
if (Objects.nonNull(objectMapper)) {
return objectMapper;
}
objectMapper = getRawObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return objectMapper;
}
}
private static ObjectMapper getRawObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addSerializer(LocalDateTime.class, new JacksonComponent.LocalDateTimeSerializer());
timeModule.addDeserializer(LocalDateTime.class, new JacksonComponent.LocalDateTimeDeserializer());
timeModule.addSerializer(LocalDate.class, new JacksonComponent.LocalDateSerializer());
timeModule.addDeserializer(LocalDate.class, new JacksonComponent.LocalDateDeserializer());
timeModule.addSerializer(Date.class, new JacksonComponent.DateSerializer());
timeModule.addDeserializer(Date.class, new JacksonComponent.DateDeserializer());
objectMapper.registerModule(timeModule);
return objectMapper;
}
}
严格来说上面的代码并不是严格的我们认识的“懒汉式”单例模式的写法,首先这是在工具类里写的,单例对象的类型也和当前类不一致,但是使用了懒汉式的“双重检测机制”,精髓是GET到了,一样能够实现ObjectMapper的全局唯一。
所以说还是得“变通”,懒汉式单例模式和饿汉式不一样,它是使用的时候才创建的,如果单例创建的条件很复杂,比如有很多外部依赖,那饿汉式就完全不能使用了,必须使用懒汉式单例模式才能行。
3、静态内部类
我将上面的Jackson案例改成静态内部类模式的,如下所示
public class JsonUtils {
private JsonUtils() {
// 私有构造函数的逻辑
}
private static class ObjectMapperHolder {
private static final ObjectMapper INSTANCE = getRawObjectMapper();
}
public static ObjectMapper getObjectMapper() {
return ObjectMapperHolder.INSTANCE;
}
private static ObjectMapper getRawObjectMapper() {
......
}
}
4、枚举模式
public enum JsonUtils {
INSTANCE;
private ObjectMapper objectMapper;
private JsonUtils() {
objectMapper = getRawObjectMapper();
}
public ObjectMapper getObjectMapper() {
return objectMapper;
}
private static ObjectMapper getRawObjectMapper() {
......
}
}
在使用的时候直接使用JsonUtils.INSTANCE
即可获取单例对象。
枚举模式可以防止序列化和反序列化过程中对单例的破坏,而且枚举类型不支持通过反射来创建新的实例,从而防止了反射攻击,它是第一推荐写法。
注意:本文归作者所有,未经作者允许,不得转载