公共类 Thread0 { private Thread0(){} // 私有的 private static Thread0实例 = null ; // 单利对象 // 静态的构造方法 公共静态 Thread0的getInstance(){ 如果(例如 ==空){ 实例 =新 Thread0(); } 返回 实例 ; }
}
//这种写法采用的是饿汉式方式实现【非线程安全的】
||
/ ** * 由Thread0 更改为线程安全 *由zhaihuilin创建于2018/6/8 15:44。 * / public class Thread1 { 私人线程1(){} // 私有的 私有静态 线程1实例 =空 ; // 单利对象 // 静态的构造方法 公共静态线程1的getInstance(){ 如果(例如 ==空){ // 双重判断双重检测机制 同步(线程1。类){ // 添加同步锁 如果(例如 == null){ // 双重判断双重检测机制 instance = new Thread1(); } } } 系统。out .println(“ 这种方式还是存在安全隐患的就是线程抢占了资源导致实例发生了变化”); 返回 实例 ; } / ** * 1. 为了防止新的Singleton 被执行多次,因此在新的操作之前加上Synchronized同步锁,锁住整个类(注意,这里不能使用对象锁) 。2. 进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程甲构建完对象, 线程乙也已经通过了最初的判空验证,不做第二次判空的话,线程乙还是会再次构建实例对象。 这种情况表面看似没什么问题,要么Instance 还没被线程A 构建,线程B 执行if (instance == null )的时候得到true ; 要么Instance 已经被线程A 构建完成,线程B 执行if (instance == null )的时候得到false 。 真的如此吗?答案是否定的。这里涉及到了JVM 编译器的指令重排。 指令重排是什么意思呢?比如java 中简单的一句instance = new Singleton ,会被编译器编译成如下JVM 指令: memory = allocate(); // 1 :分配对象的内存空间 ctorInstance(memory); // 2 :初始化对象 instance = memory; // 3 :设置实例指向刚分配的内存地址 但是这些指令顺序并非一成不变,有可能会经过JVM 和CPU 的优化,指令重排成下面的顺序: memory = allocate(); // 1 :分配对象的内存空间 instance = memory; // 3 :设置实例指向刚分配的内存地址 ctorInstance(存储器); // 2 :初始化对象 当线程A 执行完1,3,时,实例对象还未完成初始化,但已经不再指向null 。此时 如果线程B 抢占到CPU 资源,执行 if (instance == null )的结果会是false ,从而返回一个没有初始化完成的实例对象。 * / }
/ ** * 由线程1 更改为添加挥发性 修饰符阻止了变量访问前后的指令重排,保证了指令执行顺序 *创建者zhaihuilin上2018年6月8日15时44。 * / public class Thread2 { private Thread2(){} // 私有的 private volatile static Thread2 instance = null ; // 单利对象 // 静态的构造方法 公共静态线程2的getInstance(){ 如果(例如 ==空){ // 双重判断双重检测机制 同步(线程2。类){ // 添加同步锁 如果(例如 == null){ // 双重判断双重检测机制 instance = new Thread2(); } } } 系统。out .println(“ 这种方式还是存在安全隐患的就是线程抢占了资源导致实例发生了变化”); 返回 实例 ; } }