如有错误或有补充,以及任何的改进意见,请在评论区留下您的高见,同时文中给出大部分的示例
如果觉得本文写的不错,不妨点个赞,收藏一下,助力博主产生质量更高的作品
概念
单例模式(Singleton Pattern)是软件设计模式的一种,用于确保一个类只有一个实例,并提供一个全局访问点。这种模式通常用于需要频繁创建和销毁同一对象的场景,以减少系统资源的消耗和提高性能。
优缺点
优点:
实例控制:单例模式确保类只有一个实例,可以防止其他对象实例化自己的副本,从而确保所有对象都访问唯一实例。
节约资源:由于系统中只存在一个对象,可以节约系统资源,特别是在需要频繁创建和销毁对象的场景中,可以提高系统的性能。
避免对共享资源的多重占用:单例模式可以避免对共享资源的多重占用,有助于资源的集中管理和控制。
缺点:
扩展困难:由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。如果需要修改单例类的内部逻辑,则必须修改所有使用该单例的地方,不利于代码的模块化和可维护性。
职责过重:单例类通常承载了过多的职责,在一定程度上违背了“单一职责原则”。这会导致代码的复杂性增加,可读性和可维护性降低。
滥用风险:如果滥用单例模式,将一些本应该短生命周期的对象定义为单例,可能会导致内存泄漏和其他问题。例如,将数据库连接池定义为单例,可能会导致连接池溢出或资源耗尽。
状态丢失:在某些情况下,如果实例化的对象长时间不被利用,系统可能会将其视为垃圾回收,导致对象状态的丢失。
使用场景
比如Spring的IOC容器非懒加载的Bean,网页浏览计数等。
实现方式
饿汉式
饿汉式是在类加载时即实例化单例对象的一种方式。在类加载时,饿汉式单例实例就会被创建。这种方式可以确保在程序启动时就能使用单例对象,但是如果在整个应用程序中并不需要使用该单例对象,则会造成资源浪费。
示例
SingletonClass1.java
// 饿汉式 public class SingletonClass1 { // 创建唯一对象 private static SingletonClass1 instance = new SingletonClass1(); // 避免实例化 private SingletonClass1() { } // 获取唯一对象 public static SingletonClass1 getInstance() { return instance; } public void hello () { System.out.println("This is SingletonClass1"); } }
Main.java
public class Main { public static void main(String[] args) { SingletonClass1.getInstance().hello(); } }
懒汉式
懒汉式是在需要使用单例对象时才进行实例化的一种方式。在类加载时,懒汉式单例实例并不会被创建,而是在第一次调用时才创建。这种方式可以避免不必要的资源浪费,但在多线程环境下可能会出现问题。在懒汉式实现中,需要将单例的实例化放在一个公共的静态方法中,以确保线程安全。
// 懒汉式 public class SingletonClass2 { // 初始不加载 private static SingletonClass2 instance = null; // 避免实例化 private SingletonClass2() { } // 获取对象的方法 public static synchronized SingletonClass2 getInstance() { if (instance == null) { instance = new SingletonClass2(); } return instance; } public void hello() { System.out.println("This is SingletonClass2"); } }
Main.java
public class Main { public static void main(String[] args) { SingletonClass2.getInstance().hello(); } }
以上的懒汉式实现效率较低,每次都要通过synchronized,效率较低
以下是通过双重锁检查机制来使其更加高效的例子:
双重检查锁定(Double-Checked Locking)是一种在多线程环境下使用的同步机制,主要用于实现单例模式。其基本原理是在第一次检查时验证实例是否已经创建,如果已经创建则直接返回实例,不需要进行后续的同步操作。如果实例尚未创建,则进入同步块,在同步块内部进行第二次检查,以确保只有一个线程创建实例。双重检查锁定的目的是在尽量减少同步操作的情况下,保证在多线程环境中只有一个实例被创建,这样可以避免性能下降,同时保证线程安全。需要注意的是,在使用双重检查锁定时,需要使用volatile关键字修饰实例变量,以确保其可见性,避免指令重排序导致的问题。此外,由于不同版本的JVM和编译器对指令重排序的处理方式可能不同,因此在使用双重检查锁定时,需要仔细考虑各种因素,确保其正确性和可靠性。
双重锁检查机制效率更高的原因主要有以下几点:
减少同步锁的调用次数:在懒汉式单例模式中,如果不使用双重锁检查机制,每次调用 getInstance() 方法时都需要获取同步锁,这会导致性能下降。而双重锁检查机制通过第一次检查减少了获取同步锁的次数,只有在实例未创建的情况下才会进入同步块,从而提高了性能。
提升并发度,降低开销:由于双重锁检查机制在第一次检查时只进行了简单的 null 检查,而这个操作是非常快速的,开销也比较小,因此它可以快速地释放同步锁,让其他线程有机会进入同步块,从而提高了并发度。同时也可以降低开销,提高性能。
SingletonClass2Plus.java
// 懒汉式Plus public class SingletonClass2Plus { // 初始不加载 private static volatile SingletonClass2Plus instance = null; // 避免实例化 private SingletonClass2Plus() { } public static SingletonClass2Plus getInstance() { // 第一次检查 if (instance == null) { synchronized (SingletonClass2Plus.class) { // 第二次检查 if (instance == null) { instance = new SingletonClass2Plus(); } } } return instance; } public void hello() { System.out.println("This is SingletonClass2Plus"); } }
Main.java
public class Main { public static void main(String[] args) { SingletonClass2Plus.getInstance().hello(); } }