漫谈设计模式(二):单例(Singleton)模式

1.前言

       实际业务中,大多业务类只需要一个对象就能完成所有工作,另外再创建其他对象就显得浪费内存空间了,例如web开发中的servlet,这时便要用到单例模式,就如其名一样,此模式使某个类只能生成唯一的一个对象。单例模式,根据单例对象创建的时机不同,可以分为懒汉模式和饿汉模式。

2.饿汉(单例)模式

       饿汉模式,是在一开始的类加载完成后,便创建一个实例对象。此时不管项目中会不会用到这个对象,这个对象都会被创建,它会在内存中占用一定的空间。如果实在需要此对象,需要调用它的某些方法进行业务处理,那就用getInstance()直接从内存中取出这个对象的引用。

public class HungrySingleton {
    //声明一个当前类的变量为类变量,方便静态方法访问,保证还存在一个当前类的对象
    private static HungrySingleton singleInstance = new HungrySingleton();
    private String description; //其他属性
 
    /*
     * 私有化唯一的默认构造方法,使其不能在外部(其他类中)通过构造方法创建实例对象
     */
    private HungrySingleton() {} 
 
    /**
     * 返回此类的唯一对象
     * @return
     */
    /*
     * 因构造器被私有化,外部不能直接获得实例对象,也就不能调用实例方法获得对象的引用
     * (这也就陷入了死循环。外部起初没有持有singleInstance的引用,现实需求又想通过此对象来调用方法获得它的引用,
     * 另外若外部本来持有它的引用,就根本用不着调用方法来获得它的引用。)
     * 因此只能将此方法声明为与对象无关的静态方法,静态方法又不能直接访问成员变量,singleInstance只能声明为静态的类变量
     */
    public static HungrySingleton getInstance() {
        return singleInstance;
    }
 
}

3.懒汉(单例)模式

      懒汉模式,在类初始化的时候只会将singleInstance 字段(类变量)初始为空null,并不会将其实例化;只有在确实需要这个对象,需要用这个对象的方法进行业务处理时,才对其初始化。这里用到了"对象初始化延迟”的理念,这样做可以尽可能做到减少内存开锁,实现按需分配内存。而在多线程环境中,多个线程访问同一个全局变量可能会出现线程安全问题,即某个线程访问的这个变量值可能是不安全的,可能是“已过期”的值。就如一个线程刚刚通过getInstance()方法改变了singleInstance 的状态,singleInstance 已经被实例化了,而且并未退出方法,全局变量singleInstance 还未更新,但此时另一个线程也进入了getInstance()方法,却发现singleInstance 并未实例化,这个方法会再去实例化一个对象。这种情况下,单例模式就不能保证实现真的“单例”,因此方法或代码块需要设置同步。参考网上的帖子,以双重校验锁的形式在保证线程安全的同时,降低同步的性能损耗。另外在静态方法中getInstance() 中要访问singleInstance,而singleInstance是类变量,那么要保证线程安全就必须给类加锁(Class元类中默认包含类锁,对象中也默认包含对象锁),而不是给对象加锁。

public class LazySingleton {
    // 用"volatile"关键字声明,防止编译优化,改变内存分配顺序
    private static volatile LazySingleton singleInstance = null;
    // 元类锁
    private static final Class<?> CLASS_LOCK = LazySingleton.class;
     
    //对象计数器
    private static  int instanceCount=0;
 
    private LazySingleton() {
    }
 
    /**
     *  返回此类的唯一对象
     * @return
     */
    public static LazySingleton getInstance() {
        /*
         * 设置同步块,保证在多线程条件下,也只有一个对象(单线程下,不需要同步)
         *
         * 只有检测到singleInstance未被实例化时,才设置同步块。
         * 如果检测到singleInstance已被实例化,则直接返回其引用。
         * 这种双重同步验证锁,比给整个方法中的代码设为同步块或将方法声明为同步方法,性能要高得多。
         * 因为双重同步验证,只会同步一次,其另外两种思路一直都需要同步。
         *
         */
        if (singleInstance == null) {
            synchronized (CLASS_LOCK) {
                if (singleInstance == null) {
                    singleInstance = new LazySingleton();
                    instanceCount++;
                }
            }
        }
        return singleInstance;
    }
}

4.总结

      两种单例模式各有其好处,各有其适用场景。饿汉模式,不管实际需不需要这个单例对象,它都会被创建在内存中,而且会放在静态区,而静态区的内存很难被垃圾回收器(GC)所回收,这样就容易造成内存浪费。如果需要这个实例,则可以直接到内存中取这个对象的引用,而不需要再去创建它了,因此就少去了再去创建对象的时间开销。

      而懒汉模式,做到了“按需配置”,如果不需要这个单例对象,此对象便不会被创建实例化,内存中也就不会为此对象分配内存空间,这样就减少了没必要的内存消耗。但如果需要这个实例,java虚拟机再去主动实例化它,然后再返回实例化后的对象引用。在这里第一次getInstance()时获单例对象时,所花费的时间要比饿汉模式要多一些,因为多了一个实例化对象的操作。

      简单来说,饿汉模式是“以空间换时间”,而懒汉模式是“以时间换空间”。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值