设计模式中,最为基础与常见的就是单例模式。这也是经常在面试过程中被要求手写的设计模式。
下面就先写一个简单的单例:
public class Singleton {
private static Singleton singleton =new Singleton();
private void Singleton(){}
public static Singleton getSingleton(){
return singleton;
}
}
上面是饿汉式单例:jvm在启动的时候直接在内存中初始化一个单例对象,我们在调用getSingleton()时直接获取该对象。
public class Singleton {
private static Singleton singleton =null;
private void Singleton(){}
public static Singleton getSingleton(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
上面的是懒汉机制:在jvm启动的时候不会初始化单例对象,只有调用getSingleton()时才去创建对象。多线程的情况下会出现不同步问题,因此需要加锁。
public class Singleton {
private static Singleton singleton =null;
private void Singleton(){}
public static synchronized Singleton getSingleton(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
懒汉加锁之后,我们调用getSingleton()方法时,会在该方法上面加锁,每次只允许一个线程进入,可以解决同步问题,但是性能会下降。
public class Singleton {
private static Singleton singleton =null;
private void Singleton(){}
public static Singleton getSingleton(){
if(singleton==null){ @1
synchronized (Singleton.class){ @2
if(singleton==null){ @3
singleton = new Singleton(); @4
}
}
}
return singleton;
}
}
在懒汉加锁基础上编程双锁机制:解决了同步时的性能问题。但是在多线程的情况下,还是会出现问题,问题出现在哪里?
singleton = new Singleton(); @4
这一步实例化的过程有问题:
这一行代码可以分解为如下的三行伪代码:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
在JIT编译器中,可能会发生重排序。在重排的情况下:如果a,b两个线程同时调用getSingleton()方法,例如a线程先获取到,在a线程 singleton = new Singleton(); 时发生重排,执行1,3,2,执行到3,b线程获取singleton的没有被初始化。
如何解决:
第一:加关键字volatile
public class Singleton {
private static volatile Singleton singleton =null;
private void Singleton(){}
public static Singleton getSingleton(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton =new Singleton();
}
}
}
return singleton;
}
}
Volatile关键字: 可以解决可见性问题,不能确保原子性问题(通过 synchronized 进行解决), 禁止指令的重排序(单例主要用到此JVM规范)。
第二:利用静态内部类
public class Singleton {
private static Singleton singleton =null;
private void Singleton(){}
private static class StaticSingleton{
private static final Singleton SINGLETON =new Singleton();
}
public static Singleton getSingleton(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton = StaticSingleton.SINGLETON;
}
}
}
return singleton;
}
}
静态内部类确保在第一次初始化的时候,不用担心并发问题。因为jvm会负责同步整个过程,在初始化进行一半的时候,别的线程无法使用。
个人公号:【排骨肉段】,可以关注一下。