Java版设计模式之【单例模式】

319 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

概述

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。可以有效减少内存的开支,针对需要频繁创建、销毁的对象优势非常显著。

分类

单例模式有很多实现方法, 懒汉, 饿汉, 静态内部类, 枚举类,但整体分类有以下两种:

懒汉式单例

类加载不会导致该单实例对象被创建, 而是首次使用该对象时才会创建。

饿汉式单例

类加载就会导致该实例对象被创建。

起因

小张:哥,你现在有没有空,能不能来帮我看一下这接口?

我:什么情况呢?

小张:测试那边说我这个接口响应时间太长了,量一起来很多请求都超时了,让我得重新优化一下,我看了那接口就是一个查询功能,查询的SQL索引也都命中了,耗时也在合理范围内,不知道要怎么去优化他了。

我:那你把代码给我看看。

public class Client {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        // 创建数据库连接
        connection();

        // 执行查询
        select();

        // 释放连接
        destroy();

        System.out.println((System.currentTimeMillis() - start) / 1000 + "秒");
    }

    public static void connection(){
        try {
            // 模拟连接耗时
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void select(){
        try {
            // 模拟操作耗时
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void destroy(){
        try {
            // 模拟销毁耗时
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我:你这个接口查询语句我看了没什么问题,耗时太长的问题主要存在于你创建的数据库连接跟销毁的步骤上了,对于一个需要经常使用的对象来说,我们并不需要频繁的创建和销毁它,这里你可以使用单例模式来对它进行一个改造。

案例目标

这里的类加上final关键字修饰,主要是怕有子类对父类覆盖,破坏了单例。

同时,如果该类实现了序列化的话,我们还得实现一个readResovle()方法,使之在反序列化中返回你当前的对象,如果没有实现readResovle()方法的话,在反序列化时会创建一个新的实例。

饿汉模式

public final class HungrySingleton implements Serializable {
    private static HungrySingleton jdbc = new HungrySingleton();

    // 构造方法私有,保证外界无法直接实例化
    private HungrySingleton(){}
    
    public Object readResovle(){ return jdbc; }

    // 通过该方法获得实例对象
    public static HungrySingleton getInstance(){
        return jdbc;
    }
}

懒汉模式

public final class LazySingleton {
    // volatile 防止指令重排
    private static volatile LazySingleton jdbc = null;
    // 构造方法私有,保证外界无法直接实例化
    private LazySingleton(){}

    // 同步代码块缩小加锁范围,提升性能
    public static LazySingleton getInstance(){
        if (jdbc != null){
            return jdbc;
        }
        
        synchronized(LazySingleton.class){
            // 再次进行判空,防止并发问题
            if(jdbc != null){
                return jdbc;
            }
        jdbc = new LazySingleton();
        return jdbc;
    }
}

小结

final:可以防止当前类被子类覆盖。

readResovle():方法可以在反序列化时返回单前的实例对象。

volatile:可以防止指令重排。

饿汉:类加载就会导致该实例对象被创建。

懒汉:类加载不会导致该单实例对象被创建, 而是首次使用该对象时才会创建。