一.懒汉模式
- 单例模式有两种实现方式,懒汉模式和饿汉模式。懒汉模式在类加载时不被初始化。
- 常见的懒汉模式代码
public class LazySingleton {
//定义静态成员变量
private static LazySingleton lazySingleton = null;
//私有构造器,一定要有,防止在其他类中直接使用new创建
private LazySingleton(){}
//静态方法获取实例对象
public static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
- 单线程中无须考虑线程安全问题,但是在多线程中,需要使用关键字
synchronized
才能保证线程安全。 - 加入关键字
synchronized
的代码
public class LazySingleton {
//定义静态成员变量
private static LazySingleton lazySingleton = null;
//私有构造器
private LazySingleton(){}
//静态方法获取实例对象
public static LazySingleton getInstance(){
synchronized (LazySingleton.class){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
}
- 以上代码虽然保证了线程安全,但是由于使用了
synchronized
关键字,给类加了锁,在性能上会有一定的限制。
二.DoubleCheck双重检查
- 为了兼顾性能与线程安全,对以上懒汉模式代码进行改良
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if(lazyDoubleCheckSingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazyDoubleCheckSingleton == null)
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
return lazyDoubleCheckSingleton;
}
}
- 该
getInstance()
与之前的方法不同,它并没有在开始调用该方法时就上锁。当线程t1进入代码之后,判断LazyDoubleCheckSingleton
单例对象为空,就会为类加锁,其它线程处于MONITOR
状态。t1创建单例对象,此时其它线程会在对单例对象进行判断,这时单例对象不再为空。 - 以上的执行步骤看似完美,实际上是存在隐患的。
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
实际上经历了三个步骤:1.分配内存给这个对象 2.初始化这个对象 3.设置lazyDoubleCheckSingleton
指向第1步分配的地址。 - 但是,第2,3步骤顺序可能会被颠倒。在分配完内存空间之后便使用变量名指向分配的空间,此时判断单例对象是不为空的。但是由于初始化未完成,所以内存空间里没有内容。
- 为了解决这一隐患,需要使用到
volatile
。volatile
关键字为实例域的同步访问提供了一种免锁机制。如果声明了一个域为volatile
,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。