设计模式之单例模式

定义与介绍

指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

使用场景: 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。

优点:

  1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。

  2. 避免对资源的多重占用(比如写文件操作)。

缺点:

  1. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,扩展困难。

单理模式的实现

1. 懒汉式,线程不安全

使用了懒加载的设计,即只在需要调用是才进行初始化,但是不能保证线程并发安全。

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}
2. 懒汉式,线程安全

为了解决线程安全问题,直接对整个方法加锁。

但只会在第一次调用时进行初始化,这要书写会导致后续每次都只能有一个线程进行调用,效率不高。

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static synchronized Singleton getInstance() {
     if (instance == null) {
         instance = new Singleton();
     }
     return instance;
    }
}
3. 懒汉式,双重校验锁

不对整个方法上锁,只在进行初始化时,对代码块进行上锁。

问:为什么有两次校验?

答:可能有多个线程同时进入第一个 if 校验,防止重复初始化。

问:为什么加 volatile ?

答:因为 instance = new Singleton(); 这句代码并不是原子性操作,在 JVM 中大概有以下操作:

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

但是 JVM 并不一定按照 1-2-3 来的,也可能是 1-3-2

如果是第二种,当执行到 3 时,下一个线程进来,检验到的 instance 不为空,则返回进行调用,就会出现报错的情况。

public class Singleton {
    private volatile static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
     if (instance == null) {
         synchronized (Singleton.class) {
             if (instance == null) {
                 instance = new Singleton();
             }
         }
     }
     return instance;
    }
}
4. 饿汉式

类一旦加载就创建一个单例,没有加锁,执行效率提高,但是缺点也很明显,如果这个类没有被调用会浪费一定的内存空间。

且有些类是需要根据配置文件加载,或根据不同参数进行设置的,在这种情况下饿汉式写法就无法满足需求。

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){}

    public static Singleton getInstance() {
     	return instance;
    }
}
5. 静态嵌套类

这种写法的原理是:当 JVM 加载一个类时,不会去加载这个类的嵌套类。

只有当我们去调用 getInstance() 时, JVM 才会去初始化 SingletonHolder

静态属性保证了全局唯一,静态变量初始化保证了线程安全,所以这里的方法没有加 synchronized 关键字( JVM保证了一个类的 初始化在多线程下被同步加锁 )。

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

创建枚举默认就是线程安全的,所以不需要加锁。这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

public enum Singleton{
    INSTANCE;
}

总结

一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy loading )会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。如果有其他特殊的需求,可以考虑使用双重校验锁的方式。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值