java设计模式 -- 单例设计模式

388 阅读5分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

单例设计模式

1.单例设计模式的理解

  • 即某个类在全局系统中,有且仅有一个实例,并且该类提供一个方法,使得其它类可以访问该实例。 注意
  1. 单例类全局仅会创建一个对象实例。
  2. 该类必须提供一个取得该唯一实例的方法(静态方法)

2.单例模式的区分 -- 饿汉式

2.1 优缺点

优点: 实现方式比较简单, 通常来说只需要使用静态变量,或者放在静态代码块中即可实现。它在类装载的过程中就完成实例化,从jvm层面帮我们解决了线程安全问题

缺点: 在类装载时就完成实例化,没有达到 LazyLoading(懒加载)的效果,容易造成内存浪费。

2.2 举例

1.静态变量形式

//饿汉式(静态变量)

class Singleton {
   
   //1. 构造器私有化, 外部不能new
   private Singleton() {
      
   }
   
   //2.本类内部创建对象实例
   private final static Singleton instance = new Singleton();
   
   //3. 提供一个公有的静态方法,返回实例对象
   public static Singleton getInstance() {
      return instance;
   }
   
}

2.静态代码块形式

class Singleton {
   
   //1. 构造器私有化, 外部能new
   private Singleton() {
      
   }

   //2.本类内部创建对象实例
   private  static Singleton instance;
   
   static { // 在静态代码块中,创建单例对象
      instance = new Singleton();
   }
   
   //3. 提供一个公有的静态方法,返回实例对象
   public static Singleton getInstance() {
      return instance;
   }
   
}

总结:上面这两种方法的实现本质上都一样, 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载 的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果,在生产环境中可以用,但一般不推荐这种方式,容易造成内存浪费。

3.单例模式 -- 懒汉式

3.1 懒汉式(线程不安全)

class Singleton {
   private static Singleton instance;
   
   private Singleton() {}
   
   //提供一个静态的公有方法,当使用到该方法时,才去创建 instance
   //即懒汉式
   public static Singleton getInstance() {
      if(instance == null) {
         instance = new Singleton();
      }
      return instance;
   }
}

优点: 达到懒加载的效果,避免内存浪费,执行效率高

缺点: 只能在单线程环境下使用,在多线程情况下,线程不安全,容易创建多个对象实例

3.2 懒汉式(线程安全 -- synchronized关键字)

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

优点: 达到懒加载的效果,避免内存浪费,线程安全

缺点: 执行效率极低,单例对象通常使用比较多的就是getInstance()方法获取对象实例,而在本例中因为加了synchronized关键字,即使在该实例已被创建出来,每个线程想要尝试获取Singleton实例都会被阻塞不能直接返回对象实例,需要等待前面的线程释放锁。

3.3懒汉式(线程安全--双重if判断)

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;
   }
}

优点: 达到懒加载的效果,避免内存浪费,线程安全,对前面一种版本进行了改进,当该类已经被实例化后,后续线程在访问 当instance != null时直接返回即可,不会造成线程阻塞,实际开发中推荐使用这种方式

注意: 此处需加volatile关键字! 如不加volatile关键字,可能会出现,a线程已经创建了Singleton的对象实例,即将return的时候 (因为线程之间内存是独享的,instance在a线程中已经完成实例化,但还没有写到主物理),此时b线程在判断instance == null 会为true,重而又创建一个对象实例。而加了volatile关键字就可以保证变量的可见性,b线程判断就不会为空

4单例模式 -- 静态内部类

class Singleton {
   private static volatile Singleton instance;
   
   
   private Singleton() {}
   
   //写一个静态内部类,该类中有一个Singleton类型的静态属性 
   private static class SingletonInstance {
      private static final Singleton INSTANCE = new Singleton(); 
   }
   
   //提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
   public static Singleton getInstance() {
      return SingletonInstance.INSTANCE;
   }
}

特点 达到懒加载的效果,同式也保证了线程安全,实际开发中推荐使用这种方式。

原理 静态内部类,在Singleton类被装载时并不会立即实例化,而是在调用getInstance()方法时,去加载SingletonInstance类,从而完成Singleton的实例化, 因为类的静态属性,属性只会在第一次加载该类时初始化,即这里通过jvm类装载机制,保证线程的安全性。

5单例模式 -- 枚举

enum Singleton {
   INSTANCE;
}

特点 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过根据枚举的加载原理,它应该算饿汉式。

6.应用举例

6.1 java.lang.Runtime

图片.png

通过源码我们可以发现,Runtime这个类就使用到了 单例模式饿汉式

7注意事项

  1. 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用构造方法

  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)