论坛首页 Java企业应用论坛

关于单例及其范围...以及构造函数中泄漏 this

浏览 9222 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-09-02   最后修改:2010-09-09

这几天在看《深入浅出设计模式》,看到了单例模式这一章。
关于单例想必大家都很熟悉了。
而为了线程安全,HeadFirst中列出了三种方案,并对优缺点做了说明。
1.直接static synchronized getInstance().  
优点:简单。
缺点:类锁,且每次调用都会把类锁住,效率不高。--实在令人难以接受

2.急切(eagerly)实例化,也就是声明实例的引用变量时直接生成一个实例。
private static Singleton st=new Singleton();
优点:万金油,没有突出的缺点。
缺点:被类加载器加载时就可能(与JVM的实现也有关系)生成实例,而非用到时生成。--并非不可接受。

3.双重检测加锁。(关于DCL,这里有片极为精彩的论述:http://www.iteye.com/topic/260515,所有问题都可以退散了)
private volatile static BgImage singleton = null;

if(singleton==null)
        {
            synchronized(Singleton .class)
            {
                if(singleton==null)
                {
                    singleton = new Singleton ();
                }
            }
        }
优点:延迟实例化,除了第一次得到对象实例会锁住类其余情况不会锁。
缺点:java1.4之前的许多jvm,使用volatile会导致双重检测加锁失效。(HeadFirst原话)。而不使用volatile可能会因更新不及时产生多个实例的可能。第一次生成实例依然会锁类。--若非java1.4及更早版本用户,此方案可接受度最高。

很明显三种方案中没有哪种方案是完美的。

个人贪心不足希望集以上各种优点于一身,通用、延迟初始化,因此想了一种方案。
囧...发现想法有缺陷,删之.

-----------------

关于方案3双重检测失效的问题,这里有很详细的描述,不过作者的JDK比1.3还要老... - -! 还是埋了吧.
http://ajava.org/course/java/13502.html

这里有个更NB的解决方案。懒汉通用无锁。
http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
利用内部类来实现单例念头之前也闪了一下,还没细想,就看到了这篇文章。

JE之前也有过讨论
http://www.iteye.com/topic/691223?page=3

关于DCL,这里有片极为精彩的论述:http://www.iteye.com/topic/260515,所有问题都可以退散了

唉,想法失败了,内容撞墙了
版主您随意.

另外请教大家个问题
在构造方法中使用 xxx=this,尽管没有错误,但是NetBeans会提示"构造函数中泄漏 this"...在一般方法中使用没有提示...这是什么意思?会有什么问题?

 

---------------------------------------------------(9.9)

单例的范围:

今天看到了chjavach兄写的研磨设计模式系列.

关于他对"单例模式"的研磨中的"单例模式的范围"的叙述,我有不同的看法.

 

在他的研磨中是这么描述的:

"        也就是在多大范围内是单例呢?
        观察上面的实现可以知道,目前Java里面实现的单例是一个虚拟机的范围。因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的ClassLoader装载饿汉式实现的单例类的时候就会创建一个类的实例。
        这就意味着如果一个机器上有多个虚拟机,那么每个虚拟机里面都应该有一个这个类的实例,但是整个机器上就有很多个实例了。
        另外请注意一点,这里讨论的单例模式并不适用于集群环境,对于集群环境下的单例这里不去讨论,那不属于这里的内容范围。"

 

他提到的是"虚拟机的范围",并说明了"一个虚拟机在通过自己的ClassLoader装载"

多个的虚拟机拥有多个实例这固然是正确的.但在一个虚拟机中就能保证只存在一个单例类的实例吗?

答案是否定的.

chjavach兄提到了ClassLoader,却忽视了一个虚拟机能够有多个ClassLoader.一个虚拟机中不同的ClassLoader装载同一个单例类,是会得到多个实例的.

这种情况在一般的程序中可能很少见,不过在java web应用中可能就比较常见了.

因为每个web application都有一个属于自己的ClassLoader.

 

以tomcat6为例:其根目录下的lib文件夹里的jar是由一个ClassLoader(StandardClassLoader)装载的,而webapps文件夹下的应用是由WebappClassLoader装载的(且每个应用的WebappClassLoader的context不同)。StandardClassLoader是WebappClassLoader的Parent ClassLoader.

我们打包一个Singleton类放到tomcat/lib文件夹下,然后写一个拥有Singletonx的应用,这个应用下的index.jsp同时引用两个单例类

把这个应用放到webapps里,命名为webtest

第一次运行index.jsp会看到Singleton与Singletonx都是第一次装载实例化,此后每次运行index.jsp都会分别得到Singleton与Singletonx的同一个实例.

然后进入tomcat的管理界面,把webtest应用reload一次后再次运行index.jsp

这时会看到Singleton的实例仍然是reload之前的那个,而Singletonx会重新被加载并实例化一次.

因为reload会销毁webtest的ClassLoader(WebappClassLoader),但不会影响StandardClassLoader.

 

从而得出单例模式的范围是一个ClassLoader及其子ClassLoader而非整个JVM.

   发表时间:2010-09-02  
引用
2.急切(eagerly)实例化,也就是声明实例的引用变量时直接生成一个实例。
private static Singleton st=new Singleton();
优点:万金油,没有突出的缺点。
缺点:被类加载器加载时就可能(与JVM的实现也有关系)生成实例,而非用到时生成。--并非不可接受。


这个最好能声明成final,因为按照新的(java 5以后)的JMM(Java Memory Model)中对于final的语义定义,可以确保你收到的实例是完整的。没有定义成final似乎不能有这个保证。
0 请登录后投票
   发表时间:2010-09-02  
引用
缺点:java1.4之前的许多jvm,使用volatile会导致双重检测加锁失效。(HeadFirst原话)。


Java 5之前,volatile语义只定义了指定的volatile变量的内容visible,
但没有定义volatile相关的其他变量的visibility,更没有定义与其相关的
语句在执行优化时有可能会导致的reorder问题。
Java 5之后引入了新的JMM语义定义,这些问题基本得到解决。但
双重锁还不是被推荐使用,具体是什么原因,我没有认真看,不懂。
0 请登录后投票
   发表时间:2010-09-02  
引用
这里有个更NB的解决方案。懒汉通用无锁。
http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
利用内部类来实现单例念头之前也闪了一下,还没细想,就看到了这篇文章。


这是依据Java内部类装入机制设计的一种实现方法。
Java内部类只有在第一次使用时才装入。
0 请登录后投票
   发表时间:2010-09-03  
jeff.key 写道
引用
2.急切(eagerly)实例化,也就是声明实例的引用变量时直接生成一个实例。
private static Singleton st=new Singleton();
优点:万金油,没有突出的缺点。
缺点:被类加载器加载时就可能(与JVM的实现也有关系)生成实例,而非用到时生成。--并非不可接受。


这个最好能声明成final,因为按照新的(java 5以后)的JMM(Java Memory Model)中对于final的语义定义,可以确保你收到的实例是完整的。没有定义成final似乎不能有这个保证。


你确认么?static + final 修饰?常量?不能被修改属性的实例...那这个单例还有什么价值没有?
0 请登录后投票
   发表时间:2010-09-03  
static + final 修饰,不能修改实例的属性?你听谁说的
0 请登录后投票
   发表时间:2010-09-03   最后修改:2010-09-03
jeff.key 写道
引用
2.急切(eagerly)实例化,也就是声明实例的引用变量时直接生成一个实例。
private static Singleton st=new Singleton();
优点:万金油,没有突出的缺点。
缺点:被类加载器加载时就可能(与JVM的实现也有关系)生成实例,而非用到时生成。--并非不可接受。


这个最好能声明成final,因为按照新的(java 5以后)的JMM(Java Memory Model)中对于final的语义定义,可以确保你收到的实例是完整的。没有定义成final似乎不能有这个保证。


请问"实例是完整的"这句话是什么意思呢?
加了final固然最好
但在HeadFirst中并没有提到要加(实际上它确实也没加)
有没有证据证明不加会出现问题?这种说法的出处?

--谢谢icanfly在第二页给出的说法,刚好也解释了这里说的完整性的问题...不过我觉得急切加载应该不会碰上,JVM在加载这个类的时候就实例化了这个对象,不会因为多线程的问题导致这个对象不完整(多线程尚未启动)

楼上的qjtttt兄显然还没明白static+final的含义...

另外,关于双检测中volatile的必要性产生了疑问
在core java(第八版)的14.5.8章节以及think in java(第四版)的21.3.3章节
都有这么一种描述:
同步机制强制在处理器系统中,一个任务作出的修改必须在另一个任务中是可视的...如果一个域完全由synchronized方法或语句块来防护,那就不必将其设置为是volatile的.(出自think in java)
如果你用锁来保护可以被多个线程访问的代码,那么可以不用考虑这种问题(出自core java,"问题"是指可视性)

如果按照这两本书的说法,双检测中已经使用了同步锁防护,那么那个属性就没必要使用volatile了.请大家指正下.

另外,"构造函数中泄漏 this"到底是什么意思啊... - -!
0 请登录后投票
   发表时间:2010-09-03  
runshine 写道

private volatile static BgImage singleton = null;

if(singleton==null)
        {
            synchronized(Singleton .class)
            {
                if(singleton==null)
                {
                    singleton = new Singleton ();
                }
            }
        }

private static Singleton instance= null;
private final static Object classLock=new Object();
public static Singleton getInstance(){
   synchronized(classLock)
   {
         if(singleton==null)
         {
                    instance= new Singleton ();
          }
    return instance;
    }
}

没有具体研究过,对于类锁的影响会有多大。反正我的程序里是不会有影响的。
我一般这样写,不被调用也只是创建了一个object,应该可以接受吧。
synchronize一般我会放在最外层。
0 请登录后投票
   发表时间:2010-09-03  
qjtttt 写道
jeff.key 写道
引用
2.急切(eagerly)实例化,也就是声明实例的引用变量时直接生成一个实例。
private static Singleton st=new Singleton();
优点:万金油,没有突出的缺点。
缺点:被类加载器加载时就可能(与JVM的实现也有关系)生成实例,而非用到时生成。--并非不可接受。


这个最好能声明成final,因为按照新的(java 5以后)的JMM(Java Memory Model)中对于final的语义定义,可以确保你收到的实例是完整的。没有定义成final似乎不能有这个保证。


你确认么?static + final 修饰?常量?不能被修改属性的实例...那这个单例还有什么价值没有?


这位兄弟对final的作用有点误解,static + final 修饰只是说该引用不能指向别的对象了,并非说不能修改该实例的属性。
0 请登录后投票
   发表时间:2010-09-03   最后修改:2010-09-03
fool_leave 写道

private static Singleton instance= null;
private final static Object classLock=new Object();
public static Singleton getInstance(){
   synchronized(classLock)
   {
         if(singleton==null)
         {
                    instance= new Singleton ();
          }
    return instance;
    }
}

没有具体研究过,对于类锁的影响会有多大。反正我的程序里是不会有影响的。
我一般这样写,不被调用也只是创建了一个object,应该可以接受吧。
synchronize一般我会放在最外层。


你这种写法与方案1:直接static synchronized getInstance().是一样的
每一次取实例都会锁整个类.安全性与通用性上没什么问题.但在多线程中性能损失会很大.

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics