设计模式——单例模式8种实现

时间:2024-02-21 19:12:38

单例模式

1、了解概念:

单例模式是指采用一定的方法保证在整个软件系统种,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

个人理解:一个类只能实例化一个对象,其他的使用都是引用这个对象。

单例模式的八种实现方式

1、饿汉式
    a、静态常量(推荐)
    b、静态代码块(推荐)
2、懒汉式
    a、线程不安全
    b、线程安全,同步方法
    c、线程安全,同步代码块
3、双重检查(推荐)
4、静态内部类(推荐)
5、枚举(推荐)

单例模式的作用:

1、节省资源:有些对象在程序中只需要一个实例,如果反复创建多个实例会造成资源的浪费,而单例模式可以确保只创建一个实例,节约资源。
​
2、全局访问点:是单例模式中提供给其他代码访问单例对象的接口或方法,它封装了单例对象的创建和访问逻辑,使得其他部分可以方便地获取单例对象,同时可以控制访问权限和简化调用方式。
​
3、保持一致性:单例模式可以确保一个类只有一个实例,从而避免了多个实例之间状态的不一致性。
​
4、延迟初始化:有些对象的初始化比较耗时,使用单例模式可以将对象的初始化延迟到真正需要的时候进行,提高程序的性能。
​
5、线程安全:单例模式可以保证在多线程环境下只有一个实例被创建,避免了多线程并发访问时可能出现的问题。

饿汉式

1、静态常量

步骤:

1、构造器私有化

2、类的内部创建对象

3、向外暴露一个静态公共方法(getInstance)

4、代码实现

public class Singleton {
    // 创建静态常量实例
    private static final Singleton instance = new Singleton();
    
    // 私有构造函数,禁止外部通过new关键字创建实例
    private Singleton() {
        // 在此处进行初始化操作
    }
    
    // 公共方法,用于获取实例
    public static Singleton getInstance() {
        return instance;
    }
}
 

优点:

  • 写法简单,线程安全、效率高,在类装载的时候完成实例化,避免了线程同步问题

缺点:

  • 内存浪费:在类加载的时候完成了实例化,

  • 无法实现懒加载:没有达到Lazy Loading的效果

  • 不支持传参

说明:

  • 这种方式基于classloder机制避免了多线程同步问题,但是instance在类装载时就实例化,单例模式中大多数是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他静态方法导致类装载,这个时候初始化instance就没有导致lazy loading的效果

结论:

  • 这种单例模式可用,但是可能会造成浪费内存

补充:lazy loading

Lazy loading(延迟加载)是一种设计模式,它指的是在需要的时候才加载数据或资源,而不是在初始化时就立即加载。这样可以延迟创建或数据加载时间,实现节省资源和提高性能。
​
使用场景:
1、减少启动时间:延迟加载启动时不需要的资源,加快程序启动时间。
2、节省资源:有些资源可能占用较大内存或需要较长时间初始化,如果不立即加载可以节省资源。
3、提高性能:延迟加载可以根据实际需要动态加载资源,避免了一开始就加载所有资源导致性能下降。

2、静态代码块

public class Singleton {
    private static final Singleton instance;
​
    static {
        try {
            instance = new Singleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occurred in creating singleton instance");
        }
    }
​
    private Singleton() {
        // 私有构造函数
    }
​
    public static Singleton getInstance() {
        return instance;
    }
}
 

这种方式跟静态常量的优缺点差不多。

懒汉式

1、线程不安全

public class Singleton {
    private  static Singleton instance;
​
    private Singleton() {
        // 私有构造函数
    }
​
    //提供一个静态的公共方法,使用到该方法时,才创建instance
    //即 懒汉式
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
​

优缺点说明:

1、起到了Lazy Loading效果,但是只能在单线程中使用
2、如果在多线程情况,一个线程进入了if(instance == null)判断语句块,还没来得及向下执行,另一个线程也通过了这个判断语句,这是会产生多个实例。
3、总结:多线程不可使用这种方式,在实际开发中也不要使用这种方式。

2、线程安全,同步方法

public class Singleton {
    private static Singleton instance;
​
    private Singleton() {
        // 私有构造函数
    }
​
    //加入了同步,解决了线程安全问题
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
​

优缺点说明:

1、解决了线程不安全问题
2、效率太低了。每个线程想要类的实例化的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后续需要直接return就行了
3、结论:实际开发,不推荐

3、线程安全,同步代码块

public class Singleton {
    private static Singleton instance;
​
    private Singleton() {
        // 私有构造函数
    }
​
    //加入了同步,解决了线程安全问题
    public  static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class){
                instance = new Singleton();
            }
        }
        return instance;
    }
}
​

直接总结:本意是想对上种方式优化,实际并没有优化,还是会出现不同步情况。

结论:实际开发中,不能使用这种方式。

双重检查

public class Singleton {
    private volatile static Singleton instance;
​
    private Singleton() {
        // 私有构造函数
    }
​
    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查,避免不必要的同步
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查,确保只有一个实例被创建
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
​

优缺点说明:

1、Double-Check是多线程开发中常使用的,如上述代码,我们使用了两次if(singleton == null) 检查,这样就可保证线程安全了
2、特点:线程安全、延迟加载、效率较高。推荐使用
3、使用双重检查锁定实现线程安全的单例模式既可以确保线程安全,又可以尽量减少同步代码块的使用,从而提高性能。

补充:

volatile是Java的关键字。
用于:
1、声明变量具有可见性:
当一个线程修改了一个被 volatile 修饰的变量的值时,这个新值对其他线程是立即可见的,即所有线程都能看到最新的值。
​
2、禁止重排列:
volatile 关键字会禁止 JVM 对代码的优化重排序,保证指令不会被重排,从而避免出现意外的执行顺序。
​
3、不保证原子性:
volatile 关键字只能确保可见性和禁止重排序,并不能保证对 volatile 变量的操作是原子性的。如果需要保证原子性

静态内部类

public class Singleton {
    private Singleton() {
        // 私有构造函数
    }
​
    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
​
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
​

利用静态内部类特点:

  • 外部类被装载了,静态内部类不会被装载

  • 在调用getInstance方法的时候,会实现静态内部类只被装载一次

优缺点说明:

1、这种方式次啊用类装载机制来保证初始化实例时只有一个线程
2、静态内部类在Singleton类被装载时并不会立刻实例化,而是在需要实例化时才调用getInstance方法,才会装载SingletonInstance类,从而实现Singleton的实例化
3、类的静态属性智慧在第一次加载类的时候初始化,所以,在这JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程无法进入
4、优点:避免了线程不安全,利用静态内部类特点实现了延迟加载,效率高
5、结论:推荐使用

枚举

public enum Singleton {
    INSTANCE;
​
    // 可以在枚举中定义自己需要的方法和属性
    public void doSomething() {
        // 执行相应的操作
    }
}

优缺点说明:

1、通过枚举实现单例模式,可以避免多线程问题,还可防止反序列化重新创建新的对象。
2、结论:推荐使用

单例模式在JDK中使用

比如说Runtime类中使用到了饿汉式

小结:

1、饿汉式
    a、静态常量(推荐)
    b、静态代码块(推荐)
2、懒汉式
    a、线程不安全
    b、线程安全,同步方法
    c、线程安全,同步代码块
3、双重检查(推荐)
4、静态内部类(推荐)
5、枚举(推荐)

  1. 饿汉式:

    • 静态常量:推荐使用,简单、线程安全,在类加载时就创建实例。

    • 静态代码块:不太推荐,与静态常量方式类似,但可读性稍差。

  2. 懒汉式:

    • 线程不安全:不推荐,多线程环境下存在安全风险。

    • 线程安全,同步方法:不推荐,性能较差,每次获取实例都需要同步。

    • 线程安全,同步代码块:一般不推荐,和同步方法类似,同步块也会影响性能。

  3. 双重检查:

    • 推荐使用:在多线程环境下,确保了延迟加载和高性能的需求。

  4. 静态内部类:

    • 推荐使用:延迟加载、线程安全、高性能,且实现简洁。

  5. 枚举:

    • 强烈推荐使用:简洁、线程安全、防止反射和序列化破坏单例,是最佳实践之一。