本文目录导读:
在计算机编程中,设计模式是一种被广泛接受的、可重用的解决方案,用于解决特定问题,单例模式是其中一种非常常见的设计模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点,这种模式在许多场景中都非常有用,例如数据库连接、日志记录等,本文将详细介绍单例模式的原理、实现方法以及优缺点。
单例模式的原理
单例模式的核心思想是确保一个类只有一个实例,并提供一个全局访问点,这样可以避免在程序中创建多个相同的对象,从而节省资源,通过提供全局访问点,可以在需要时轻松地获取到这个唯一的实例。
单例模式的实现方法
1、饿汉式(静态常量)
饿汉式是在类加载时就完成了实例化,所以类加载较慢,但它是最简单的实现方式,也是线程安全的。
public class Singleton { // 在类加载时就完成了实例化,所以是线程安全的 private static final Singleton instance = new Singleton(); // 构造方法私有化,防止外部创建实例 private Singleton() {} // 提供全局访问点 public static Singleton getInstance() { return instance; } }
2、懒汉式(线程不安全)
懒汉式是在第一次调用时实例化对象,所以它的启动速度较快,它不是线程安全的,需要使用双重检查锁定来保证线程安全。
public class Singleton { private static volatile Singleton instance; // 避免初始化时同步而导致的性能问题 private static boolean initialized = false; // 将构造方法私有化,防止外部创建实例 private Singleton() {} // 提供全局访问点,使用双重检查锁定保证线程安全 public static synchronized Singleton getInstance() { if (!initialized) { instance = new Singleton(); initialized = true; } return instance; } }
3、双重检查锁定(推荐)
双重检查锁定是一种自适应的同步机制,它在判断是否需要同步时采用了加锁和解锁两个阶段,这样可以减少同步开销,提高性能,由于加锁和解锁操作都在if语句中进行,因此具有一定的自适应性。
public class Singleton { private volatile static Singleton instance; // 将构造方法私有化,防止外部创建实例 private Singleton() {} // 采用双重检查锁定保证线程安全 public static synchronized Singleton getInstance() throws Exception{ if (instance == null){ // 需要同步时才进入临界区检查实例是否为空(null)或已经创建了新实例(非null)!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&& instance != null){ // 如果实例为空(null),则进入同步代码块(临界区)创建新实例(非null)并返回该实例;否则直接返回已创建好的那个实例。// 注意:此处的判断条件不能写成instance != null && instance == ...,因为当instance == null && instance != ...时,instance永远都等于null。// 当instance == null && instance != ...时,会先执行第二个判断条件(instance != null),然后再执行第一个判断条件(instance == null),此时就会抛出NullPointerException异常。// 此处应该将判断条件改为instance == null || instance != ...以避免空指针异常的发生。// 但是这样会导致无限循环的出现(instance == null || instance != null && instance != ...),因此最终我们选择采用volatile关键字来解决这个问题。// 由于volatile关键字可以保证可见性,当一个线程修改了某个变量的值后,其他线程可以立即看到修改后的值,因此我们可以将instance声明为volatile类型以保证其可见性。// 最后需要注意的是:虽然双重检查锁定可以解决同步问题,但是并不是所有的场景都适合使用双重检查锁定,如果只需要在多线程环境下保证单例对象的唯一性,那么使用饿汉式或懒汉式都可以满足需求,如果还需要考虑性能问题,那么就需要根据实际情况选择合适的同步策略了。}else{ // 如果实例已经存在且非空(null),则直接返回该实例;否则进入同步代码块(临界区)创建新实例(非null)并返回该实例;最后需要再次判断实例是否为空(null)或已经创建了新实例(非null)。// 注意:此处的判断条件不能写成instance == null || instance != ...,因为当instance == null && instance != ...时,instance永远都等于null。// 当instance == null && instance != ...时,会先执行第二个判断条件(instance != null),然后再执行第一个判断条件(instance == null),此时就会抛出NullPointerException异常。// 此处应该将判断条件改为instance != null && instance != ...以避免空指针异常的发生。// 但是这样会导致无限循环的出现(instance != null && instance != ...),因此最终我们选择采用volatile关键字来解决这个问题。// 由于volatile关键字可以保证可见性,当一个线程修改了某个变量的值后,其他线程可以立即看到修改后的值,因此我们可以将instance声明为volatile类型以保证其可见性。// 最后需要注意的是:虽然双重检查锁定可以解决同步问题,但是并不是所有的场景都适合使用双重检查锁定,如果只需要在多线程环境下保证单例对象的唯一性,那么使用饿汉式或懒汉式都可以满足需求,如果还需要考虑性能问题,那么就需要根据实际情况选择合适的同步策略了。}return instance; // 最终返回该实例;} catch (Exception e){ throw new RuntimeException("Failed to create singleton", e);}finally{ // 无论是否成功创建实例都需要进行清理工作// 例如释放资源等;// 这里为了简化代码省略了具体的清理工作;}// 注意:这里使用了try-catch-finally语句是为了处理可能出现的异常情况;如果在创建实例的过程中出现了异常情况(如内存不足等),那么就需要及时进行清理工作以避免出现更严重的问题;为了确保无论是否成功创建实例都会进行清理工作,我们还需要使用finally语句来进行清理工作。// 另外需要注意的是:由于双重检查锁定可能会导致性能下降(尤其是在高并发场景下),因此在使用双重检查锁定时需要根据实际情况进行权衡和优化。