/**
* 单例模式-双重校验锁
* @author szekinwin
*
*/
public class SingleTon3 {安全
private SingleTon3(){}; //私有化构造方法
private static volatile SingleTon3 singleTon=null;
public static SingleTon3 getInstance(){性能
//第一次校验
if(singleTon==null){ 优化
synchronized(SingleTon3.class){线程
//第二次校验对象
if(singleTon==null){
singleTon=new SingleTon3();
}
}
}
return singleTon;
}
内存
public static void main(String[]args){
for(int i=0;i<200;i++){
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+":"+SingleTon3.getInstance().hashCode());
}
}).start();
}get
}
}同步
注意事项:
问题:为何须要两次判断if(singleTon==null)?hash
分析:第一次校验:因为单例模式只须要建立一次实例,若是后面再次调用getInstance方法时,则直接返回以前建立的实例,所以大部分时间不须要执行同步方法里面的代码,大大提升了性能。若是不加第一次校验的话,那跟上面的懒汉模式没什么区别,每次都要去竞争锁。class
第二次校验:若是没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2得到锁,建立实例。这时t1又得到CPU执行权,因为以前已经进行了第一次校验,结果为null(不会再次判断),得到锁后,直接建立实例。结果就会致使建立多个实例。因此须要在同步代码里面进行第二次校验,若是实例为空,则进行建立。
须要注意的是,private static volatile SingleTon3 singleTon=null;须要加volatile关键字,不然会出现错误。问题的缘由在于JVM指令重排优化的存在。在某个线程建立单例对象时,在构造方法被调用以前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就能够将分配的内存地址赋值给instance字段了,然而该对象可能尚未初始化。若紧接着另一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。
(4)静态内部类:一样也是利用了类的加载机制,它与饿汉模式不一样的是,它是在内部类里面去建立对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会建立单例对象,从而实现懒汉式的延迟加载。也就是说这种方式能够同时保证延迟加载和线程安全。