版权声明:原创文章,转载需注明转载出处! https://blog.csdn.net/zhoumingsong123/article/details/82832599
//First Version
public class SingletonKerriganA {
/**
* 单例对象实例
*/
private static SingletonKerriganA instance = null;
public static SingletonKerriganA getInstance() {
if (instance == null) { //line A
instance = new SingletonKerriganA(); //line B
}
return instance;
}
}
//由于不支持并发,所以再改进
//Second Version
public class SingletonKerriganB {
/**
* 单例对象实例
*/
private static SingletonKerriganB instance = null;
public synchronized static SingletonKerriganB getInstance() {
if (instance == null) {
instance = new SingletonKerriganB();
}
return instance;
}
}
//这里synchronized 保证对instance的赋值是原子的,可见的,有序的。
//但是性能不好,因为当第一次这个被new之后(需要做同步),以后多线程就不需要
//做同步(没有修改),直接返回即可。
//因此改成以下版本
//Third Version
public class SingletonKerriganD {
/**
* 单例对象实例
*/
private static volatile SingletonKerriganD instance = null; //这里为什么要加volatile,不加不行
//吗?
//因为这里instance是引用,其可以被两个线程同时拥有,因此两个线程都可以读取修改其对
//象。所以得保证instance的修改立即可见。
//(如果某个线程修改了变量,便立即将变量写回到主内存,
//如果某个线程使用的话,都是从主内存读取值,x86处理器缓存一致性协议
//注意:volatile并不保证对变量的操作是原子性的。)
//对于基本类型变量可以直接从主内存复制到工作内存中然后修改。那对象怎么办,特别大的对象怎么解决?(猜测:每个对象最终也是基本类型组成的,只拷贝待修改的基本类型,最后写回)
//当线程1在对instance赋值时(这个操作是原子的,中间不会被中断,但是instance应用在其他地方还有,所以别的线程可以偷窥到。)
//另外一个线程可以通过instance来查看其引用的对象。(2,3可能存在重排序,3在前,所以并没有对对象进行初始化,而此时线程2已经读取到instance不为空直接返回,然后该线程就拥有违背初始化的对象,可能会造成严重错误。而使用了volatile,2,3就不会重排序(为什么通过volatile (cpu lock指令),可以实现2,3的不重排?不是只是通过lock来讲instance修改的结果立即写回到主内存嘛,2,3又没数据依赖),所以instance要么为null,要么指向已经初始化的对象。)
public static SingletonKerriganD getInstance() {
if (instance == null) { //当instance为空的时候,需要new ,要保证同步
synchronized (SingletonKerriganD.class) {
if (instance == null) {
instance = new SingletonKerriganD();
//这条语句可以分配为三个操作
// 1.分配内存
// 2. 初始化对象
// 3. 将instance指向该内存。
}
}
}
return instance; //当instance不为空的时候直接返回,不需要线程同步
}
}