Java设计模式之懒汉单例模式
//懒汉式单例模式
public class LazySingleton {
private static LazySingleton lazySingleton=null;
//私有构造,不允许外面 new 对象
private LazySingleton(){}
public static LazySingleton getInstance(){
if (lazySingleton==null){
lazySingleton=new LazySingleton();
}
return lazySingleton;
}
}
//新建一个Runnable
public class T implements Runnable {
@Override
public void run() {
LazySingleton lazySingleton=LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName()+lazySingleton);
}
}
//调用
public static void main(String[] a){
Thread t1=new Thread(new T());
Thread t2=new Thread(new T());
t1.start();
t2.start();
}
//结果:返回的是同一个实例。
Thread-1com.zk.javatest.singleton.lazy_singleton.LazySingleton@f63ae59
Thread-0com.zk.javatest.singleton.lazy_singleton.LazySingleton@f63ae59
这里如果是单线程调用没有问题,如果是多线程调用,则存在隐患。
//多线程时,这段代码可能被多次执行。
if (lazySingleton==null){
lazySingleton=new LazySingleton();
}
下面我们调试一下多线程, 右击红色断点,Suspend方式选择Thread,则可进行多线程调试。
上面调试多线程可以看到,LazySingleton创建了两次,因此多线程时存在的这种隐患,既可能导致资源浪费,也违背了单例原则。
解决这个问题的方法也很简单,在静态方法那里加同步锁即可。
public class LazySingleton {
private static LazySingleton lazySingleton=null;
private LazySingleton(){}
//增加同步锁synchronized即可解决多线程问题。
public synchronized static LazySingleton getInstance(){
if (lazySingleton==null){
lazySingleton=new LazySingleton();
}
return lazySingleton;
}
}
增加同步锁虽然解决了上面的问题,但是带来了新的问题,同步锁需要加锁和解锁,这会增加开销,而且锁的范围比较大,影响性能。
为了解决同步锁带来的问题,采用双检查机制:
//懒汉单例模式之双检查
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if (lazyDoubleCheckSingleton==null){
//在里面加锁,缩小加锁区域
synchronized(LazyDoubleCheckSingleton.class){
//双检查
if (lazyDoubleCheckSingleton==null){
lazyDoubleCheckSingleton=new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
好了,到这貌似一切都完美了。是吗?事实上并没有,上面的双检查机制也有一个问题,重排序。
lazyDoubleCheckSingleton=new LazyDoubleCheckSingleton();
上面这行代码实际上有三步:
1.给对象分配内存空间;
2.初始化对象;
3.让对象指向已分配的内存空间;
重排序:为什么有重排序?系统为了提高性能,可能会重排序。即:
1.给对象分配内存空间;
3.让对象指向已分配的内存空间;(执行完这步后,lazyDoubleCheckSingleton对象不为null了)
2.初始化对象;
所以,多线程时就可能存在访问未初始化的对象lazyDoubleCheckSingleton,导致报错。
第一种解决方法就是使用 volatile 关键字来禁止重排序 。
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;
第二种解决方式就是采用静态内部类。
多线程时,某个线程获取到初始化对象锁之后,初始化对象时即使重排序了,由于加了锁,其他线程无法并行对同一对象进行初始化,因此可以解决上面的问题。
//静态内部类的单例模式
public class InnerSingleton {
private InnerSingleton(){}
private static class InnerClass{
private static InnerSingleton innerSingleton=new InnerSingleton();
}
public static InnerSingleton getInstance(){
return InnerClass.innerSingleton;
}
}
//调用
InnerSingleton innerClass=InnerSingleton.getInstance();