单例模式我们在代码设计中会经常用到。但是在多线程情况下,如果没有贴别处理,往往结果并非我们所期望的。一般单例模式的代码如下:
这种写法的确能够保证同步,但是每次在获取实例的时候,都需要获取同步锁,这就比较耗时,效率不高。于是,就有了方案三和方案四。
public class Singleton{
private static Singleton sInstance = null;
private Singleton(){
//init
}
public static Singleton getInstance(){
if ( sInstance == null){
sInstance = new Singleton();
}
return sInstance;
}
}
这块代码在单线程中时没有问题的,但是假设有两个线程同时调用getInstance()方法,两个线程进入if后发现说Instance是null,都会去实例化一个Singleton对象,这样的话就不是单例了。下面就详细分析一下几种解决方案。
方案一:加载时就实例化。
private static Singleton sInstance = new Singleton();
这种方法违背了java的lazy-load原则,即使用时才加载,本例中sInstance的所在类和sInstance的类型一样,就不存在这个问题了,但是,假若Singleton的构造方法中有比较耗时的造作时,就会大大加大了类加载的时间。
方案二:synchronized关键字,代码修改如下:
public class Singleton{
private static Singleton sInstance;
private Singleton(){
//init
}
public static synchronized getInstance(){
if (sInstance == null){
sInstance = new Singleton();
}
return sInstance;
}
}
这种写法的确能够保证同步,但是每次在获取实例的时候,都需要获取同步锁,这就比较耗时,效率不高。于是,就有了方案三和方案四。
方案三:减少获取同步锁的次数
public static getInstance(){ if (sInstance == null){ syncInit(); } return sInstance; } private void synchronized syncInit(){ if (sInstance == null){ sInstance = new Singleton(); } }
这种方案和方案二相比,只在单例没有被实例化的时候才会去竞争同步锁,大大的降低了时间的消耗。
方案四:利用JVM在加载class的时候是同步的原理
public class Singleton{ private static class SingletonContainer{ static Singleton sInstance = new Singleton(); } private Singleton(){ //init } public static getInstance(){ return SingletonContainer.sInstance; } }
JVM在加载class的时候有一种lazy-load机制,即在需要的时候才去加载,所以当我们第一次调用getInstance方法的时候,JVM才会去加载SigletonContainer类,并且JVM有同步机制保证SigletonContaner类只被加载一次,这样就保证了sIntance只被实例化一次。