思维盛宴之设计模式-单例模式Singleton Pattern

1. 适用场景

单例模式可能是众多设计模式中相对最简单的一种了。它能够保证在一个jvm虚拟机中单例类只有一个实例。通常,在应用软件系统中,线程池,缓存,日志,工具或其他拥有管理功能的类都被实现为单例,因为他们在整个系统中只需要存在一份,也只需要对外提供一个全局的访问点即可。

单例的实现有三种方式:

  • 懒汉式
  • 饿汉式
  • 登记式

登记式的底层实现依然是懒汉式或饿汉式(将不同类的单例们存放在一个内部的Map中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,先登记,再返回),因此这里就不做讨论了。我们主要来看前两种,也是大家见得最多的两种。

2. 代码实现

2.1 懒汉式

public class Singleton {

    private Singleton() {}
    //全局单例
    private static Singleton instance =null;

    //静态方法
    public static Singleton getInstance() {
        if (instance == null) {             //(1)
            instance = new Singleton();
        }
        return instance;
    }
}

上面的代码就是一个最简单的懒汉式单例实现。通过private访问修饰符限定构造器对外不可见,因此无法在外部手动创建一个Singleton实例,同时对外提供一个全局的访问点getInstance方法,以统一的控制对象的获取。这个获取过程在内部保证了对象只会创建一份。注意,这里的全局单例指的是在同一个jvm中的全局单例。

要指出的是,实际上即使构造函数被设置为private,java中的反射机制(如通过setAccessible方法设置方法可见并随后直接调用构造器)仍然可以对其进行调用并实例化一个单例类,这里对这种情况暂不做考虑。

上述的懒汉式单例实现在多线程并发的情况下是不安全的,因为多个线程可能同时执行到(1)处,发现不存在单例实例,于是各自构造器出一个实例,单例存在多个副本,状态不一致了,也就失去了其作用。

那么如果想要把上面的代码变成线程安全的,如何做呢?

有三种方法:

  1. getInstance方法上加同步

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
  2. double check:双重判断

    //全局单例
    //使用volatile关键字来声明单例对象(new Singleton()是非原子操作)
    private static volatile Singleton instance = new Singleton();
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) { //双重检查防止再次new出实例
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
  3. 使用静态内部类改造

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

方法3既可以实现线程安全(类加载时已自行实例化INSTANCE),又能够避免同步带来的性能影响。

2.2 饿汉式

private Singleton() {}
//全局单例
private static Singleton instance = new Singleton();

//静态方法
public static Singleton getInstance() {
    return instance;
}

饿汉式与上面的方法3有点相似,在类初始化时就已经创建好一个静态的全局单例供系统使用,并且jvm的类加载机制保证了这个类只被加载一次,因此它也是线程安全的。

3. 思考与总结

饿汉式依靠jvm的类加载机制天生实现了线程安全,而懒汉式本身则是线程不安全的,使用上述的3种方法可以对其改造,其中第3种在性能上更优(因为避免了同步)。

通常,饿汉式的实现方式已经能够满足绝大多数需求,性能不错,代码也少,方便且易理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值