单例模式的五种写法
单例模式
创建唯一类、唯一的对象、有唯一的全局访问点
1,五种写法
1)懒汉式
代码
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
效果1:线程不安全
原因:多个线程并行调用getinstance()的时候,会创建多个实例
解决方案1:将整个getinstance()方法设为同步(synchronized)
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
效果2:做到了线程安全,解决了多实例问题。但不高效,因为任何时候只能有一个线程调用getinstance()方法,但同步操作是只需要在第一次调用时才被需要,也就是说第一次创建实例对象的时候,也就是说当有多个线程时,同步操作对第2个线程始是不起作用的。因此可引出双重检验锁来解决这个问题。
2)双重检验锁
简单来说,就是一种使用同步块加锁的方法,称其为双重检验锁,是因为它有两次检查instance=null ,一次是在同步块外,一次是在同步块内,在同步块内检查就是为了防止多个线程一起进入同步块外的if里,从而造成多个实例的问题。
public static Singleton getSingleton(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
效果3:仍在instance = new Singleton()这里存在一个问题,这并非是一个原子操作,实际JVM进行了3件事。也就是给instance分配内存、调用Singleton的构造函数来初始化成员变量、将instance对象指向分配的内存空间(执行完这步后,instance就为非null了)。但因为JVM的即时编译器中存在指令重排序的优化,所以第2和第3步的执行顺序是不能保证的。当3先执行完后,instance 已经是非空了,但没初始化 ,所以第二个线程将会直接返回instance,然后使用。因此存在多线程问题.
解决方案2:将instance变量声明成volatile即可,volatile具有可见性,在这可以解决JVM的指令重排序优化问题,volatile是禁止这个的。也就是volatile变量的赋值操作后面会有一个内存屏障,读操作不会被重排序到内存屏障之前。
public class Singleton(){
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getSingleton(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
效果4:volatile只有在java 5中及之后才可避免JVM重排序问题。因此推出了比较简单的实现线程安全的单例模式的方法,让我们继续往下说。
3)饿汉式
public class Singleton(){
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
效果:单例的实例被声明了static和final变量,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。但这不是懒加载模式,因为会在加载类后就初始化,就算客户端没调用getInstance()方法。
4)静态内部类(最好的)
public class Singleton(){
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
5)枚举(非常简单)
public enum EasySingleton{
INSTANCE;
}
效果:不用担心双重检验锁问题,也能防止反序列化导致重新创建新的对象