今天给大家讲的一个设计模式是单例模式,它是一种广泛应用的设计模式,该模式的目的就是要保证在 JVM 中唯一实例的存在。比如我们常用的Spring开发框架,Spring Bean默认就是以单例形态存在于Spring容器中的。这个模式看似简单,但认真追究下去,其实没那么简单。单例模式有很多种写法,让我们来看一下:
一)饿汉模式
public class Singleton{
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
饿汉模式在JVM加载Singleton时就会创建实例,生命周期跟随JVM,缺点是有点浪费内存空间;
接下来我们再看懒汉模式,
二)懒汉模式一(静态内部类)
public class Singleton{
private Singleton(){}
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
静态内部类模式和饿汉模式比,优势是只有在调用 getInstance ()方法时,类的唯一实例对象才创建,从而达到延迟加载的效果。
我们再看另外一种懒汉模式,
三)懒汉模式二(双重校验模式)
public class Singleton{
private static volite Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null) {//语句1
sycnchrosized(Singleton.class) {//语句2
if(instance == null){//语句3
instance = new Singleton();//语句4
}
}
return instance;
}
}
其中,如果instance不声明volite类型,会由于 Java编译器 会进行指令重排,导致返回的instance在没有初始化的情况下被返回。怎么理解呢?假设有两个 线程 A和B,线程A先拿到了对象锁,执行到了语句4,这个语句实际是有以下三个原语组成:1、分配一块内存memory;2、在该内存上初始化Singleton对象;3、最后将该内存地址赋值给instance;但由于指令重排,会导致3在2前面先发生,此时线程B执行到语句1时,发现instance不为空,直接返回instance实例,但此实例是没经过初始化的,当访问instance的成员变量时,就有可能造成空指针异常;
最后一种创建单例的模式是枚举模式,这也是《Effective Java》作者推荐的一种方式,但实际工作中,很少有攻城狮使用此种方法。
四)枚举模式
public enum Singleton{
A;
public void someMethod(){
dosomething();
}
}
Singleton.A.someMethod();
总结如下:
留一个思考题给大家,上面提到的前三种单例模式,一定会保证JVM中是单例吗?