Java单例模式:掌握创建线程安全的高效单例实例的五种方法

单例模式是一种常用的软件设计模式,它的核心目标是确保一个类只有一个实例,并提供该实例的全局访问点。

为什么要使用单例模式

资源共享和控制:单例模式通常用于管理共享资源,如数据库连接、线程池或配置管理,确保所有用户或线程都访问同一资源。
性能考虑:创建对象可能消耗较多资源,单例模式可以保证在应用程序的生命周期中只创建一个实例,从而节省资源。
协调多个部分操作:在系统的不同部分之间提供一个共享访问点,以便进行协调操作。
如何实现一个单例模式:

  1. 饿汉式:在类加载时就创建实例,这种方式简单且线程安全,但在资源利用上可能不如懒汉式高效。

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

  1. 懒汉式(线程不安全):在第一次调用时才创建实例,这种方式可以实现懒加载,但需要注意线程安全问题。
public class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

以上代码存在的问题:当第一个线程执行到if (INSTANCE == null)时,发现INSTANCE为null。在第一个线程还未完成INSTANCE = new Singleton()这一步时,第二个线程也来到了if (INSTANCE == null)这个判断。由于第一个线程还没有将新创建的实例赋值给INSTANCE,第二个线程也会认为INSTANCE是null,然后也会创建一个Singleton实例。为了避免多线程的多次创建问题,我们通过双重校验锁来完成。

  1. 双重校验锁:结合了懒汉式和线程安全的优点,通过同步锁和两次null检查来确保只创建一个实例。
  • 首先,将instance变量声明为volatile,以确保其在多线程环境下的可见性。
  • 将构造方法私有化,防止外部创建实例。
  • 在getInstance()方法中,首先进行第一次检查,如果instance为null,则进入同步块。
  • 在同步块中,进行第二次检查,如果instance仍然为null,则创建一个新的Singleton实例。
public class Singleton {
    private static volatile Singleton INSTANCE;

    private Singleton() {}

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

  1. 静态内部类:利用Java的类加载机制来保证线程安全,同时实现了懒加载。内部类在初始化过程中,如果发现多个线程同时访问该类,只有一个线程能够完成初始化,其他线程需要等待初始化完成后才能继续执行。且在初始化完成后,静态内部类的实例对象会被创建,并且只有一个实例对象存在,其他线程访问时都会获取到同一个实例对象。
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

  1. 枚举:通过枚举类型来实现单例,这种方式更加简洁,自动支持序列化机制,绝对防止多次实例化。
public enum Singleton {  
    INSTANCE;  
}  

应用场景

  1. 配置管理:在应用程序中,配置信息如数据库连接、系统参数等通常只需要一份,使用单例模式可以确保这些配置信息的一致性和高效访问。
  2. 日志记录器:日志记录器通常需要在整个应用程序中使用,而且所有的日志信息都应该输出到同一个地方。单例模式可以保证日志记录器的唯一性,简化了日志管理。
  3. 驱动程序对象:操作系统中的驱动程序对象,如打印机驱动或显卡驱动,通常只需要一个实例与硬件通信。
  4. 线程池:线程池管理着一组工作线程,用于执行并发任务。使用单例模式可以确保整个应用程序中只有一个线程池实例,避免了资源的浪费。
  5. 缓存:缓存的目的是提高数据访问速度,单例模式可以保证缓存的一致性,避免多个实例之间的数据不一致问题。
  6. 对话框管理器:在图形用户界面(GUI)应用程序中,对话框管理器通常负责创建和管理所有对话框实例。单例模式可以确保只有一个对话框管理器实例,从而统一管理对话框的创建和销毁。

单例模式存在的问题

线程安全:尤其是在多线程环境下,如何确保只创建一个实例是一个挑战。
反序列化重新创建对象:通过序列化和反序列化可能会破坏单例的唯一性。
反射攻击:通过反射可以绕过私有构造器创建新的实例。
扩展困难:单例类不能被继承,这限制了其灵活性。

你可能感兴趣的:(设计模式,单例模式,java,开发语言)