public class SingleTonTest {
//单例全解
//第一种,懒汉模式(非线程安全的写法)
private SingleTonTest(){}; //私有化构造函数,因为不能随便让其new对象,故采用private私有化构造函数。
private static SingleTonTest instance = null; //创建单例对象
public static SingleTonTest getInstance(){ //获取单例对象的方法
if (instance == null){ //如果单例初始值是null,说明对象还没有被创建,
//(当instance刚被初始化,此时若有两个线程同时访问,instance还是空的,两个线程会同时调用getInstance方法,
//此时,两个线程都会判断通过,开始执行new,就会创建两个对象。)
instance = new SingleTonTest(); //则创建对象
}
return instance; //并返回instance对象返回
}
}
public class SingleTonTest {
// 第二种。(线程安全的)
private SingleTonTest() {}; // 私有化构造函数,因为不能随便让其new对象,故采用private私有化构造函数。
private static SingleTonTest instance = null; // 创建单例对象
public static SingleTonTest getInstance() { // 获取单例对象的方法
if (instance == null) { // 双重检测机制
synchronized (SingleTonTest.class) { // 同步锁。为了防止new SingleTonTest 被多次执行,synchronized,每次只允许一个线程访问对象
if (instance == null) { // 进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,
// 线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。
instance = new SingleTonTest(); // 则创建对象
}
}
}
return instance; // 并返回instance对象返回
}
//此时也不是绝对的线程安全
}
//指令重排是:比如java中简单的一句 instance = new Singleton,会被编译器编译成如下JVM指令:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
//但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:
memory =allocate(); //1:分配对象的内存空间
instance =memory; //3:设置instance指向刚分配的内存地址
ctorInstance(memory); //2:初始化对象
//当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,
//执行 if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。
//如何避免这一情况,我们需要在instance对象前面增加一个修饰符volatile。
public class SingleTonTest {
//第三种
private SingleTonTest() {} // 私有构造函数
private volatile static SingleTonTest instance = null; // 单例对象
public static SingleTonTest getInstance() { // 静态工厂方法
if (instance == null) {
synchronized (SingleTonTest.class) {
if (instance == null) {
instance = new SingleTonTest();
}
}
}
return instance;
}
}
//经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
//如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。