我最早学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的写法要好呢?
写法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的写法要好呢?
那楼主查一下ClassLoader类的加载方法,是不是线程安全的,不就行了么 ? 如果,类加载器加载一个类的动作是线程安全的,初始化静态变量也包含在这一动作里,那不就没啥可疑的问题了么?两种方式,侧重点应该是是否采用lazy吧?(就是延迟还是不延迟的问题)本人才疏学浅,希望达人们批评指正,但,别骂我啊~
synchronized (Test.class) {
保证了初始化instance时是线程安全的,而且getInstance方法是可并发的。
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.htmlDouble-checked locking and the Singleton pattern
http://www.ibm.com/developerworks/java/library/j-dcl.html
你不加 volatile 的话,那很有可能在并发的情况下产生不止一个实例。因为第一个线程在实例化的时候,第二个线程进来时看到的可能是之前的东西(这是 JAVA 内存模型决定的)可以看一下这篇文章:volatile 变量使用指南
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
我现在还有个问题,就是用第一种单例方法,private static final Test instance = new Test();
在类初始化时,会赋值instance为Test对象,那么类初始化是线程安全的么?我只知道类加载ClassLoader.loadClass没有synchronized 关键字,不清楚类初始化是否是线程安全。如果类初始化不是线程安全的,那么第一种方法依旧可能产生多个事例。
java.net.URLClassLoader#loadClass
sun.misc.Launcher$AppClassLoader#loadClass这些方法都有 synchronized 啊?
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费力又没效果,下周一重构代码,再次感谢火龙果