单例模式

单例模式

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例

1. 饿汉式

public class Singleton{
    //类加载时就初始化
    private static final Singleton instance = new Singleton();

    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}
  • 这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的
  • 缺点:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

2. 懒汉式,线程不安全

//多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
public class Singleton {
    private static Singleton instance;
    private Singleton (){}
    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}

3. 懒汉式,线程安全

解决上述问题最简单的方法是将整个 getInstance() 方法设为同步(synchronized)

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

虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。

4. 双重检验锁

为了使同步操作只在第一次调用时才被调用,引入双重检验锁

public class Singleton {
    private static Singleton instance;
    private Singleton (){}
    public static Singleton getSingleton() {
      if (instance == null) {                         //Single Checked
          synchronized (Singleton.class) {
              if (instance == null) {                 //Double Checked
                  instance = new Singleton();
              }
          }
      }
      return instance ;
  }
}
  • 缺陷:instance = new Singleton() 是非原子性操作,可能包含的操作有:

    1. instance 分配内存
    2. 调用 Singleton 的构造函数来初始化成员变量
    3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

    JVM中可能会出现指令重排,有可能出现1-3-2的顺序,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错,故出现了用volatile关键字

5. 双重检验锁+volatile

public class Singleton {
    private volatile static Singleton instance; //声明成 volatile
    private Singleton (){}
    public static Singleton getSingleton() {
        if (instance == null) {                         
            synchronized (Singleton.class) {
                if (instance == null) {       
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

使用volatile 的原因是不仅因为具有可见性,更重要的是禁止指令重排序优化

6. 静态内部类

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

7. 枚举 Enum

public enum EasySingleton {
     INSTANCE;
     public void print(){
     System.out.println("hello,world");
     }
}

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。

8. 优缺点和使用场景

  • 优点:

    1. 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显
    2. 由于单例模式只生成一个实例,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
    3. 可避免对资源的多重占用,例如读写文件动作,由于只有一个实例存在内存中,避免对同一个文件的同时写操作
    4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问
  • 缺点:

    1. 单例模式一般没有接口,扩展困难
    2. 不利于测试
    3. 与单一职责原则冲突
  • 应用场景:
    1. 要求生成唯一序列号的环境
    2. 在整个项目中需要一个共享访问点或共享数据(WEB页面的计数器)
    3. 创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源
    4. 需要定义大量的静态常量和静态方法的环境
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值