双校验的单例模式

44 阅读2分钟

完成了单例模式的实现并做了一个检查的逻辑,代码说话。至于单例对象为什么要加volatile关键字,看过的书中解释说在对象创建的时候有开辟内存空间、对象初始化、地址赋值给对象引用这些步骤,那众所周知volatile修饰的对象会禁止编译器或是cpu操作时进行重排序,所以就不会出现并发情况下对象刚被给予地址引用但未初始化完成就被第一个if判断为false,进而导致return一个异常对象,当然我并不知道编译器的心情,所以我也不知道它什么时候会重拍序,检验方法在5000线程数的情况下没有出现非单例对象的情况..

/**  
* 2024/3/6  
* kyosli 双重校验的单例模式  
* demo0304  
*/  
public class Demo2 {  
private static volatile SingletonObject singletonObject = null;  
  
//线程存储获取到的单例对象hashCode值的集合  
private static final ConcurrentHashMap<Long, Integer> checkMap = new ConcurrentHashMap<>(5000);  
  
public static SingletonObject getSingleton() {  
if (singletonObject == null) {  
synchronized (Demo2.class) {  
if (singletonObject == null) {  
//这里的初始化方法容易受到编译器指令重拍的影响,比如先获得对象地址空间的引用而并没有进行初始化  
//间接导致18行代码错误判断,但是这个概率目前我没有测出来  
singletonObject = new SingletonObject("单例对象");  
}  
}  
}  
return singletonObject;  
}  
  
public static void main(String[] args) {  
//创建线程模拟并发请求  
for (int i = 0; i < 5000; i++) {  
new Thread(() -> {  
SingletonObject singleton = getSingleton();  
checkMap.put(Thread.currentThread().getId(), singleton.hashCode());  
}).start();  
}  
//注意这里不要用Long类型的数组,会造成NPE,因为引用类型数组初始化后里面的值数null  
long[] arr = new long[100];  
int i = 0;  
Collection<Integer> values = checkMap.values();  
//将hashCode分散到数组上,最后通过校验数组元素之间的值来判断是否有非单例对象出现  
for (Integer value : values) {  
arr[i % 100] += Long.valueOf(value);  
i++;  
}  
for (int j = 1; j < arr.length; j++) {  
if (!Objects.equals(arr[j], arr[j - 1])) {  
System.out.println("出现非单例对象");  
break;  
}  
}  
}  
}