1.什么是单例模式
在内存中只会创建一次且仅创建一次对象的设计模式。在程序中多次使用同一个 对象且作用相同时,为了防止频繁的创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一对象。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
2.单例模式的类型
·懒汉式:在真正需要使用对象时才去创建该单例类对象
·饿汉式:在类加载时已经创建好该单例对象,等待程序使用
·枚举
懒汉式创建单例对象
懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已经实例化直接返回该类对象,否则先执行实例化操作。
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if (singleton==null) {
singleton=new Singleton();
}
return singleton;
}
}
饿汉式创建单例对象
饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。默认认为程序在启动时已经创建好了这个对象。
public class Singleton {
// private static Singleton singleton;
// private Singleton(){}
// public static Singleton getInstance(){
// if (singleton==null) {
// singleton=new Singleton();
// }
// return singleton;
//
// }
private static final Singleton singleton=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
3.懒汉式在多线程下的表现
懒汉式前提判断对象是否为空,但是如果有两个线程同时判断对象没有实例化二者都会去实现一个Singleton对象,就变成双例了。这就是线程安全问题,一般我们都会在方法上加锁,或者对类对象加锁。
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (singleton==null) {
singleton=new Singleton();
}
return singleton;
}
public static Singleton getInstance(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
这样是规避了线程安全问题
但引来另一个问题:每次去获取对象都要先获取锁,并发性能非常差。
Double Check 双重校验+Lock 加锁
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {//如果不为空直接返回对象,为空则判断下面
synchronized (Singleton.class) {多个线程进行争抢 只允许一个线程 首先再次判断是否为空 然后论到其他线程时判断不为空直接输出对象
if (singleton==null) {
singleton = new Singleton();
} }
}
return singleton;
}
}
4.指令重排
创建对象,在JVM中会经历三步:
(1)为singleon分配内存空间
(2)初始化singleton对象
(3)将singleton指向分配好的内存空间
指令重排是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行,尽可能提高程序的性能
在这三步中,第二三步有可能会发生指令重排现象,创建对象顺序变为1-3-2,会导致多线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常。
使用volatile关键字可以防止指令重排序
5.枚举实现单例模式
优势1:代码对比饿汉式与懒汉式来说,更加地简洁
其次,既然是实现单例模式,那这种写法必定满足单例模式的要求,而且使用枚举实现时,没有做任何额外的处理。
优势2:它不需要做任何额外的操作去保证对象单一性与线程安全性
我写了一段测试代码放在下面,这一段代码可以证明程序启动时仅会创建一个 Singleton 对象,且是线程安全的。
我们可以简单地理解枚举实现单例的过程:在程序启动时,会调用Singleton的空参构造器,实例化好一个Singleton对象赋给INSTANCE,之后再也不会实例化
优势3:使用枚举可以防止调用者使用反射、序列化与反序列化机制强制生成多个单例对象,破坏单例模式