单例模式

GitHub代码

场景描述:假设我们需要一个全局唯一的实例,比如线程池,缓存等。

初步解决:比如所有开发人员通过约定,或者全局变量来实现。
问题:将对象赋值给一个全局变量,但并不一定会立即使用它,从而占用内存资源,造成资源的浪费。

更好的方案:使用单例模式。

经典的单例模式代码如下:

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton(){}

    public static Singleton getInstance(){
        if (uniqueInstance == null){
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

单件模式确保一个类只有一个实例,并提供一个全局访问点。

这种做法对资源敏感的对象特别重要。

相应的类图:
在这里插入图片描述
新的问题:假设我们的程序是一个多线程的程序,如果使用上述经典代码就会出现多个单例对象的奇特情况。因为我们缺少了同步。

第一种解决方案:将方法变为同步方法

public class Singleton_One {
    private static Singleton_One uniqueInstance;

    private Singleton_One(){}

    private static synchronized Singleton_One getInstance(){
        if (uniqueInstance == null){
            uniqueInstance = new Singleton_One();
        }
        return uniqueInstance;
    }
}

这种方法带来的缺陷是每次访问方法都会进行同步,造成性能的下降。而我们实际上只需要在第一次进行同步即可。

第二种方法,不使用延迟实例化,而是急切的创建实例

public class Singleton_Two {
    private static Singleton_Two uniqueInstance = new Singleton_Two();

    private Singleton_Two(){}

    public static Singleton_Two getInstance(){
        return uniqueInstance;
    }
}

这种方法的缺陷类似于使用全局变量的问题,虽然提前创建了实例,但我们并不能保证创建了后就立即使用,也会造成一定程度的浪费。

第三种方法双重检查加锁,曾经的一种经典做法。

public class Singleton_Three {
    private volatile static Singleton_Three uniqueInstance;

    private Singleton_Three(){}

    public static Singleton_Three getInstance(){
        if (uniqueInstance == null){
            synchronized (Singleton_Three.class){
                if (uniqueInstance == null){
                    uniqueInstance = new Singleton_Three();
                }
            }
        }
        return uniqueInstance;
    }
}

只有在第一次进行同步,并且使用了volatile变量避免了重排序造成的问题,但一方面由于其自身的缺陷,另一方面同步性能问题的提升,导致这种解决方案已经变为历史,不推荐使用,复杂且难以理解。

第四种方法延迟初始化占位类

public class Singleton_Four {
    private static class ResourceHolder{
        public static Resource resource = new Resource();
    }

    public static Resource getResource(){
        return ResourceHolder.resource;
    }
}
public class Resource {
    // 一个资源类,需要被单例加载
}

当任何线程第一次调用getResource时,都会使ResourceHolder被加载和被初始化,此时静态初始化器将执行Resource的初始化操作。这种方法在第二种提前实例化的方法上改进而来,并且不需要同步。因此最推荐在多线程情况下使用该方法。

注意:每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次。如果这样的事情发生在单例上,就会产生多个单例并存的怪异现象。所以如果你的程序有多个类加载器又同时使用了单例模式,请小心。

总结:当你的程序中迫切需要在全局只需一个实例对象的情况时,考虑使用单例模式,并推荐使用延迟初始化占位类方式避免多线程问题。但过度使用单例模式有可能适得其反,所以在设计时需要慎重考虑。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值