问个关于线程安全两种单例写法优略的问题

crowgns 2010-01-29 01:57:15
我最早学java的时候,学的单例的写法是:
写法1
   
public class Test(){
private static final Test instance = new Test();
private Test(){
}
public Test getInstance(){
return instance;
}
}

这种写法在类初始化时就将instance赐值。后来又学到了一种线程安全的double check方法:
写法2
   
public class Test(){
private static Test instance = null;
private Test(){
}
public Test getInstance(){
if (instance == null) {
synchronized (UserDao.class) {
if (instance == null) {
instance = new Test();
}
}
}
return instance;
}
}

这种写法的好处是保证了多线程下也只有一个Test实例。今天突然想到,就第一种写法来说,因为类是在第一次执行static方法时初始化(单例不能new所以肯定先执行static方法),而初始化类是线程安全的么?
  如果初始化类是线程安全的,那么private static final Test instance = new Test();instance只会初始化一次,只有一个new Test()对象,跟第二种写法相比,不考虑第一次instance赐值,常规来看每次比第二种方法少执行了一次判断:if(instance == null).那就没有任何使用第二种double check的单例的方法的理由。
  如果初始化类不是线程安全的,即多个线程会同时初始化Test类,那instance可能被赋值多次,即可能new多个Test对象,可能浪费一些堆区的内存。但即使是这样,评估一下,多个线程同时初始化Test类的instance变量导致new了多个对象的概率一般会相当低吧,跟第二种写法相比,少执行一次判断,整体来说是不是比有可能多占了一点内容性能上要好呢?更何况即使new了多个对象也只有一个被"可见"的引用,其他对象都可以正常被gc回收,所以说是不是第一种写法比double check的写法要好呢?
...全文
484 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
crowgns 2010-01-30
  • 打赏
  • 举报
回复
jdk1.6,
java.lang.ClassLoader#loadClass方法定义:

public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{....
}

我之前只看过loadClass(String name)没有synchronized,没去看实现是靠有synchronized的loadClass(name, false),所有的系统ClassLoader加载类都是线程安全的,惭愧...
又看了点资料,类的初始化,是由jvm统一负责并且保证保证线程安全,所以第一种方法绝对没问题,而double check费力又没效果,下周一重构代码,再次感谢火龙果
crowgns 2010-01-29
  • 打赏
  • 举报
回复
我发现我第二种方法一行写错了,同步锁那部是用的Test.Class锁
synchronized (Test.class) {
Gen_Sun 2010-01-29
  • 打赏
  • 举报
回复
学习了
  • 打赏
  • 举报
回复
java.lang.ClassLoader#loadClass
java.net.URLClassLoader#loadClass
sun.misc.Launcher$AppClassLoader#loadClass

这些方法都有 synchronized 啊?
randomer 2010-01-29
  • 打赏
  • 举报
回复
双重检查在java编译器上是行不通的,Test类的初始化与instance变量的赋值顺序不可预料。所以第二种办法不能用
haffy 2010-01-29
  • 打赏
  • 举报
回复
看不懂第2种以及楼主解释!!!有double check这样的说法嘛??研究中
xiesisi3 2010-01-29
  • 打赏
  • 举报
回复
学习了。
keeya0416 2010-01-29
  • 打赏
  • 举报
回复
饿汉式也只是浪费一个对象的空间而已,
如果调用的多的话还是用 饿汉式吧 不用总是判断
crowgns 2010-01-29
  • 打赏
  • 举报
回复
我彻底看了一遍相关材料,就double check来说确实要加volatile 修饰Instance才能真正保证正确的单例,否则可能有线程拿到没初始化完全的Test对象。这点很感谢火龙果的指导。
我现在还有个问题,就是用第一种单例方法,private static final Test instance = new Test();
在类初始化时,会赋值instance为Test对象,那么类初始化是线程安全的么?我只知道类加载ClassLoader.loadClass没有synchronized 关键字,不清楚类初始化是否是线程安全。如果类初始化不是线程安全的,那么第一种方法依旧可能产生多个事例。



[Quote=引用 17 楼 bao110908 的回复:]
引用 15 楼 crowgns 的回复:
你给的第一个链接我大约看了一下了,我很迷茫的是1public Helper getHelper() 居然不是static方法可能是他笔误;2synchronized(this),他加的是this锁 ,即对象锁,而不是class锁,比较我写的,synchronized (Test.class) ,加的是class的锁,我怀疑是他加的对象锁所以不能运行。可能double check一定要在1.5以上运行,但不需要volatile 关键字,因为我目前的项目就是jdk1.5,单例用的double check模式,而并没有加volatile 关键字。

引用 12 楼 bao110908 的回复:
由于 JAVA 内存模型的限制,第二种方法在 JAVA 中行不通!

  要使用第二种方法的话,必须在 JDK 5 或以上的版本,并且在 private static Test instance = null; 加上 volatile 关键字。

  因此,建议使用第一种方法。



你不加 volatile 的话,那很有可能在并发的情况下产生不止一个实例。因为第一个线程在实例化的时候,第二个线程进来时看到的可能是之前的东西(这是 JAVA 内存模型决定的)

可以看一下这篇文章:

volatile 变量使用指南
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
[/Quote]
z649010333 2010-01-29
  • 打赏
  • 举报
回复
学习
ublearning 2010-01-29
  • 打赏
  • 举报
回复
饿汉式单例模式,在第一次加载时就实例化
懒汉式单例模式,在第一次被引用时开始实例化
饿汉式比较好
ouyangyh 2010-01-29
  • 打赏
  • 举报
回复
这只是单例模式的加载方法的问题
第一种是绝对会实例化这个对象,从而绝对会占有一定的内存空间(因单例模式中可以该对象占的空间都会大些)
第二种是在需要的时候才实例化对象,这样如果不需要这个对象就不会占空间
使用哪种方式,是根据实际需求决定的
如果在实际应用中这个单例对象一定会用到,那就绝对要使用第一种模式
如果是不确定的,在运行空间又不太足的时候,就可以考虑第二种
不过实际应用来讲,我感觉第一种方式的多些,简单而且又安全
  • 打赏
  • 举报
回复
还有,不要怀疑 13 楼第一篇文章的权威性,看一下那篇文章的作者就知道了。
py330316117 2010-01-29
  • 打赏
  • 举报
回复
不懂,学习。(设计模式学的不好,觉得没什么用)
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 crowgns 的回复:]
你给的第一个链接我大约看了一下了,我很迷茫的是1public Helper getHelper() 居然不是static方法可能是他笔误;2synchronized(this),他加的是this锁 ,即对象锁,而不是class锁,比较我写的,synchronized (Test.class) ,加的是class的锁,我怀疑是他加的对象锁所以不能运行。可能double check一定要在1.5以上运行,但不需要volatile 关键字,因为我目前的项目就是jdk1.5,单例用的double check模式,而并没有加volatile 关键字。

引用 12 楼 bao110908 的回复:
由于 JAVA 内存模型的限制,第二种方法在 JAVA 中行不通!

要使用第二种方法的话,必须在 JDK 5 或以上的版本,并且在 private static Test instance = null; 加上 volatile 关键字。

因此,建议使用第一种方法。

[/Quote]

你不加 volatile 的话,那很有可能在并发的情况下产生不止一个实例。因为第一个线程在实例化的时候,第二个线程进来时看到的可能是之前的东西(这是 JAVA 内存模型决定的)

可以看一下这篇文章:

volatile 变量使用指南
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
xietingyan 2010-01-29
  • 打赏
  • 举报
回复
java has no double check,《多处理器编程的艺术》P42
crowgns 2010-01-29
  • 打赏
  • 举报
回复
你给的第一个链接我大约看了一下了,我很迷茫的是1public Helper getHelper() 居然不是static方法可能是他笔误;2synchronized(this),他加的是this锁 ,即对象锁,而不是class锁,比较我写的,synchronized (Test.class) ,加的是class的锁,我怀疑是他加的对象锁所以不能运行。可能double check一定要在1.5以上运行,但不需要volatile 关键字,因为我目前的项目就是jdk1.5,单例用的double check模式,而并没有加volatile 关键字。

[Quote=引用 12 楼 bao110908 的回复:]
由于 JAVA 内存模型的限制,第二种方法在 JAVA 中行不通!

要使用第二种方法的话,必须在 JDK 5 或以上的版本,并且在 private static Test instance = null; 加上 volatile 关键字。

因此,建议使用第一种方法。
[/Quote]
冰思雨 2010-01-29
  • 打赏
  • 举报
回复
楼主意识到的第一个问题,其实很好解决,因为,那个static后面的方法,是在类加载到虚拟机的时候就被调用的。
那楼主查一下ClassLoader类的加载方法,是不是线程安全的,不就行了么 ? 如果,类加载器加载一个类的动作是线程安全的,初始化静态变量也包含在这一动作里,那不就没啥可疑的问题了么?

两种方式,侧重点应该是是否采用lazy吧?(就是延迟还是不延迟的问题)

本人才疏学浅,希望达人们批评指正,但,别骂我啊~
jsmith_a2009 2010-01-29
  • 打赏
  • 举报
回复
又学到了
  • 打赏
  • 举报
回复
参考:

The "Double-Checked Locking is Broken" Declaration
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Double-checked locking and the Singleton pattern
http://www.ibm.com/developerworks/java/library/j-dcl.html
加载更多回复(3)

62,621

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧