职业IT人-IT人生活圈

 找回密码
 成为会员
搜索
查看: 2148|回复: 9

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

[复制链接]
愚人 发表于 2011-8-29 09:07 | 显示全部楼层 |阅读模式
这几天在看《深入浅出设计模式》,看到了单例模式这一章。
关于单例想必大家都很熟悉了。
而为了线程安全,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.

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


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

已经来了吗 发表于 2011-8-29 09:07 | 显示全部楼层
引用
缺点:java1.4之前的许多jvm,使用volatile会导致双重检测加锁失效。(HeadFirst原话)。


Java 5之前,volatile语义只定义了指定的volatile变量的内容visible,
但没有定义volatile相关的其他变量的visibility,更没有定义与其相关的
语句在执行优化时有可能会导致的reorder问题。
Java 5之后引入了新的JMM语义定义,这些问题基本得到解决。但
双重锁还不是被推荐使用,具体是什么原因,我没有认真看,不懂。

 楼主| 愚人 发表于 2011-8-29 09:07 | 显示全部楼层
引用
这里有个更NB的解决方案。懒汉通用无锁。
http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom
利用内部类来实现单例念头之前也闪了一下,还没细想,就看到了这篇文章。


这是依据Java内部类装入机制设计的一种实现方法。
Java内部类只有在第一次使用时才装入。

Jethro 发表于 2011-8-29 09:08 | 显示全部楼层
jeff.key 写道
引用
2.急切(eagerly)实例化,也就是声明实例的引用变量时直接生成一个实例。
private static Singleton st=new Singleton();
优点:万金油,没有突出的缺点。
缺点:被类加载器加载时就可能(与JVM的实现也有关系)生成实例,而非用到时生成。--并非不可接受。


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


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

醉倚西风 发表于 2011-8-29 09:08 | 显示全部楼层
static + final 修饰,不能修改实例的属性?你听谁说的

天上智喜 发表于 2011-8-29 09:08 | 显示全部楼层
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"到底是什么意思啊... - -!

能文能武 发表于 2011-8-29 09:08 | 显示全部楼层
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一般我会放在最外层。

Jethro 发表于 2011-8-29 09:08 | 显示全部楼层
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 修饰只是说该引用不能指向别的对象了,并非说不能修改该实例的属性。


叫我小乖 发表于 2011-8-29 09:08 | 显示全部楼层
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().是一样的
每一次取实例都会锁整个类.安全性与通用性上没什么问题.但在多线程中性能损失会很大.


您需要登录后才可以回帖 登录 | 成为会员

本版积分规则

QQ|手机版|小黑屋|网站帮助|职业IT人-IT人生活圈 ( 粤ICP备12053935号-1 )|网站地图
本站文章版权归原发布者及原出处所有。内容为作者个人观点,并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是信息平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽造成漏登,请及时联系我们,我们将根据著作权人的要求立即更正或者删除有关内容。

GMT+8, 2024-4-19 10:27 , Processed in 0.170457 second(s), 20 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表