设计模式(一):单例模式(下)

Published on 2024-02-28 17:55 in 分类: 博客 with 狂盗一枝梅
分类: 博客

在上一篇文章《设计模式(1):单例模式(上)》中讲解了单例模式的定义以及四种写法,最后着重讲解了懒汉式单例模式的线程安全性问题,但是没有讲解单例模式在实际coding的时候怎么用的,这篇文章将会讲解四种单例模式的应用案例。

1、饿汉式

饿汉式单例模式在实际工作中比较少见,但是在java核心类库中有个类使用了很经典的饿汉式单例写法:java.lang.Runtime

java.lang.Runtime 类是 Java 核心库提供的一个与运行时环境进行交互的类。它允许 Java 应用程序与操作系统进行通信、执行系统级操作,并提供了一些管理应用程序运行时的方法。

image-20240228150059545

为什么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 即可获取单例对象。

枚举模式可以防止序列化和反序列化过程中对单例的破坏,而且枚举类型不支持通过反射来创建新的实例,从而防止了反射攻击,它是第一推荐写法。


#设计模式
目录