先贴出一些概念,对后面的讲解有一定帮助
JMM(java内存模型)
根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
线程若要对某变量进行操作,必须经过一系列步骤:首先从主存复制/刷新数据到工作内存,然后执行代码,进行引用/赋值操作,最后把变量内容写回Main Memory。Java语言规范(JLS)中对线程和主存互操作定义了6个行为,分别为load,save,read,write,assign和use,这些操作行为具有原子性,且相互依赖,有明确的调用先后顺序。
网上关于单例模式的实现已经很多,一搜一大堆,此处不再赘述,先看代码
public class SingletonClass {
private static SingletonClass instance = null;
if (instance == null ) {
synchronized (SingletonClass. class ) {
if (instance == null ) {
instance = new SingletonClass();
}
}
}
return instance;
}
private SingletonClass() {
}
}
以上代码是一个典型的DoubleCheckLock实现的单利模式,相信大家都会写,这里只说问题
1).关于DCL实现的单例模式失效的问题
2).如何实现更好的单例模式
1.关于DCL失效的问题,争论主要是这段代码的问题
if (instance == null) {
instance = new SingletonClass();
}
如果线程A执行到 instance = new SingletonClass(),在 new SingletonClass()时为对象分配了内存地址,并将内存地址指向了引用instance,但对象并未完成初始化,只是分配了内存,此时线程B执行到if (instance == null) 发现instance已经存在引用,就会直接被返回,但是这时的对象还未初始化完成,只是分配了内存空间,就造成了线程B拿到的是一个无效的引用对象,这就造成了DCL失效。
以上是参考网上的说法进行理解的,但是根据JMM概念该这样理解:
但是从JAVA2以后,JMM发生了根本的改变,分配空间,初始化,调用构造方法只会在线程的工作存储区完成,在没有向主存储区复制赋值时,其它线程绝对不可能见到这个过程.而这个字段复制到主存区的过程,更不会有分配空间后没有初始化或没有调用构造方法的可能.在JAVA中,一切都是按引用的值复制的.向主存储区同步其实就是把线程工作存储区的这个已经构造好的对象有压缩堆地址值COPY给主存储区的那个变量.这个过程对于其它线程,要么是resource为null,要么是完整的对象.绝对不会把一个已经分配空间却没有构造好的对象让其它线程可见
个人觉着上面的理解是正确的。所以在java2之后就不会存在DCL失效的情况。
2.如何更好的实现单例
1)在判断为空的对象上加violatile关键字。violatile:线程在每次使用变量的时候,都会读取变量修改后的最的值
2)使用静态内部类实现单例,这也是很多地方推荐的
public class SingletonClass {
private SingletonClass() {}
publicSingletonClass getInstance(){
return ClassInner.instance;
}
private class ClassInner(){
private static SingletonClass instance = new SingletonClass();
}
}
单例模式的分析到这就结束了,第一次写blog,语言组织有待加强,希望以后会更好吧