Java单例模式常见方法分析

单例模式可以保证系统中一个类只有一个实例存在。
基于这个特性,我们可以想到,由于不会创建多余的实例,所以能够节省一定的内存,另外也可以用来控制资源的使用。
下面记录了常见的单例模式代码。

1.懒汉式 和 饿汉式

/**
 * 懒汉式单例
 * @author zhangyaoyu
 *
 */
public class SingletonLazy {
    private static SingletonLazy instance;

    private SingletonLazy() {}

    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

}
/**
 * 饿汉式单例
 * @author zhangyaoyu
 *
 */
public class SingletonHungry {
    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry() {}

    public static SingletonHungry getInstance() {
        return instance;
    }

}

他们两很好区分,在类创建时就new出对象,就是饿汉式,饥饿的时候有吃的肯定就会要;在获取实例时才new,说明他很懒,显然就是懒汉式了。

把他们放在一起,是因为他们都是平时工作中几乎不会用到的,但是可以很简单的实现单例模式的代码。
为什么他们几乎不会用到呢?
懒汉式不能保证线程安全,但是可以实现懒加载。
饿汉式虽然是线程安全的,但即使我们不需要,它也会被加载,占用内存。
既然不会用到,为什么我还要写出来呢?
因为方便我们理解啊。

那有没有即是线程安全,有是懒加载的方法呢?

2.双重检查锁

/**
 * 双重检查锁机制
 * @author zhangyaoyu
 *
 */
public class SingletonDoubleCheck {
    private static volatile SingletonDoubleCheck instance;

    private SingletonDoubleCheck() {}

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

这种我们在平时的工作中就有可能见到了,要注意的是volatile关键字,如果没有volatile,也是不能完全保证线程安全的,这个涉及到JVM的指令重排,详细需要了解volatile的作用。
看看这个方法,像不像懒汉式呢?双重检查锁其实就是懒汉式进化得来的。
那么问题来了,饿汉式能不能进化为什么常见的方法呢?

3.静态内部类

/**
 * 静态内部类
 * @author zhangyaoyu
 *
 */
public class SingletonStatic {

    private static class LazyHolder {
        private static final SingletonStatic instance = new SingletonStatic();
    }

    private SingletonStatic() {}

    public static SingletonStatic getInstance() {
        return LazyHolder.instance;
    }

}

这种方法利用了JVM静态属性只会在第一次加载类的时候初始化的特性,帮助我们实现了线程安全的单例模式。而在外部又不能访问到私有的内部类,只有getInstance()的时候,才会创建instance,实现了懒加载。

在保证了线程安全和懒加载后,我们又有两个新问题了:
1.不能防止反射来重复构建对象。
2.不能防止反序列号重复构建对象。
这两个问题虽然不常见,但是如果遇到了怎么办?

4.枚举

/**
 * 枚举
 * @author zhangyaoyu
 *
 */
public enum SingletonEnum {
    instance;

    public void function() {
        // TODO
    }
}

使用枚举,既可以防止反射,又可以防止反序列号造成的重复构建对象的问题。
不过缺点是,牺牲掉了懒加载。

5.最后答疑

问:工作中常用什么方法?
答:双重检查锁和静态内部类较为常用。一般来说我们很少遇到需要防止反射导致重复构建对象的情况,而反序列化相比之下要常见一些。在处理反序列化时,除了枚举,可以通过实现readResolve方法来解决反序列化的问题。

问:Spring默认是单例模式创建bean,那么我们有必要在使用Spring的同时自己实现单例模式吗?
答:不需要。

问:Spring是用的什么方法实现单例模式的?
答:Spring框架对单例的支持是采用单例注册表的方式进行实现的。因为以上5种方法的构造方法都是私有的,不能继承。单例注册表简单的说就是使用Map来存放创建好的实例,使用的时候从Map中取出来。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据