简单概括你的问题,如果初始化发生在释放锁之前不会有什么问题,如果初始化发生在释放锁之后就有可能有问题。
- if (instance == null) { //0
- synchronized (Singleton.class) {// 1
- if (instance == null) {// 2
- instance = new Singleton();// 3
- //这里是不是只有instance初始化完整,他才会返回?
- return instance;// 4
- }
- }
if (instance == null) { //0
synchronized (Singleton.class) {// 1
if (instance == null) {// 2
instance = new Singleton();// 3
//这里是不是只有instance初始化完整,他才会返回?
return instance;// 4
}
}
是不是就能解决问题?
第三步可以看作一个赋值语句,只不过是调用构造函数初始化在付值语句之后。另外一个线程得到锁后就看到当前的instence已经不是null了就直接返回了,这个时候有可能第一个线程初始化工作做了一半,或者没有做。这样后面的线程得到的对像就会有问题。我感觉是这样的。
还有new的时候它为什么不会初始化完整了才,释放锁?
照这样理解,我是不是也可以认为第二种,在new的时候,只初始化一半,就释放了锁,其他线程进来不是一样看到的instance也不是null,而照样返回一个不完整的实例?
- public class Singleton {
- private volatile static Singleton instance = null;
- private Singleton() {}
- public static Singleton getInstance() {
- if (instance == null) { //0
- synchronized (Singleton.class) {// 1
- if (instance == null) {// 2
- instance = new Singleton();// 3
- }
- } //4
- }
- return instance;
- }
- }
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { //0
synchronized (Singleton.class) {// 1
if (instance == null) {// 2
instance = new Singleton();// 3
}
} //4
}
return instance;
}
}
看上面的代码如果线程一执行语句3因为JVM编译器的优化工作会在构造方法实例化对像之前从构造方法返回指向该对像的引用。此时并没有执行构造方法。
然后程序执行出4此时开始执行构造方法当执行到一半的时候线程的时间片到期,此时并没有完成,线程二在0处结束等待并开始执行发现instance不为空就返回了。
我这样说你能理解吗?
第三步可以看作一个赋值语句,只不过是调用构造函数初始化在付值语句之后。另外一个线程得到锁后就看到当前的instence已经不是null了就直接返回了,这个时候有可能第一个线程初始化工作做了一半,或者没有做。这样后面的线程得到的对像就会有问题。我感觉是这样的。
还有new的时候它为什么不会初始化完整了才,释放锁?
照这样理解,我是不是也可以认为第二种,在new的时候,只初始化一半,就释放了锁,其他线程进来不是一样看到的instance也不是null,而照样返回一个不完整的实例?
总之楼主总结的不错。能用的这几种方式都总结出来了。
第三步可以看作一个赋值语句,只不过是调用构造函数初始化在付值语句之后。另外一个线程得到锁后就看到当前的instence已经不是null了就直接返回了,这个时候有可能第一个线程初始化工作做了一半,或者没有做。这样后面的线程得到的对像就会有问题。我感觉是这样的。
这样的写法是能避免无序写入的问题。因为别的线程进入不了方法体,除非当前线程释放锁。这样就能确保实例化完成。我的理解对吗?
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
正如你所说的用ThreadLocal能解决这个问题,担是我认为你的代码里面的写法是不是有问题。
public class Singleton {
private static final ThreadLocal perThreadInstance = new ThreadLocal();
private static Singleton singleton ;
private Singleton() {}
public static Singleton getInstance() {
if (perThreadInstance.get() == null){
// 每个线程第一次都会调用
createInstance();
}
return singleton;
}
private static final void createInstance() {
synchronized (Singleton.class) {
if (singleton == null){
singleton = new Singleton();
}
}
perThreadInstance.set(perThreadInstance);
}
}
倒数第三行perThreadInstance.set(perThreadInstance);应该写成perThreadInstance.set(singleton);
除此之外每一个线程都会有一个singleton的副本这样一样会造成资源浪费。本来单态模式就是想节省资源的,这样与模式的初衷不相符吧。
还有这种ThreadLocal方式的解决方案能不能应用在分布式情形,不同的JVM,ClassLoader?
我认为第二种方法同步方法加锁对像是this,而第三种加载方式是同步当前类的类对像。所以单从范围上面来讲只是if (instance == null)这一句的区别。
第二种加锁的对象不是this,其实也是 Singleton.class 锁对象,以前我测试过。为什么说是Singleton.class而不是this呢,最简单的理由就是该方法是静态的,这就很明显了:静态方法是不能访问this的。
其实第二也等同于下在面:
- public class Singleton {
- private volatile static Singleton instance = null;
- private Singleton() {}
- public static Singleton getInstance() {
- synchronized (Singleton.class) {
- if (instance == null) {
- instance = new Singleton();
- }
- }
- return instance;
- }
- }
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
第二种与第三种唯一的区别就是第三种多了“instance == null”的条件,但该条件的检测不是放在同步块中的,正是因为这一点,导致了双重检测失效!
我认为第二种方法同步方法加锁对像是this,而第三种加载方式是同步当前类的类对像。所以单从范围上面来讲只是if (instance == null)这一句的区别。