一、模式定义
单例模式保证了一个类只有一个实例,并提供一个全局访问点。
二、使用场景
重量级对象,在创建时只需要创建一个即可,如数据库连接池,线程池。
比如我们在获取一个Connection时,我们不需要反复的去创建连接池,只要在开始的时候,创建一个连接池的单例对象,那么全局去使用这个对象去取Connection就可以了。
三、创建方法
1.懒汉式
懒汉式:延迟加载,在真正使用时才创建实例。
1)线程安全问题
2)加锁 并duoble check 进行优化
3)编译器(JIT)或者CPU可能对指令重排序,导致有线程使用到未初始化的实例。使用volatile关键字防止指令码重排序。
代码如下(示例):
public class LazySingleton {
//volatitle防止指令码重排序,static 该属性属于类而不属于实例对象,所有实例对象共用一个类对象
private volatile static LazySingleton instance;
//构造器私有,不允许其他位置创建实例
private LazySingleton(){}
//为什么要加synchronized关键字
//在单线程情况下,同时创建实例,两个引用指向同一个实例
//但是多线程情况下,两个线程同时创建实例,则引用指向不同实例,因此需要加上synchronized
public static LazySingleton getInstance(){
if(instance==null){
synchronized (LazySingleton.class){
if(instance==null){ //在判断一次,防止多个线程同时进入第一个判断后继续创建多个实例
instance=new LazySingleton();
}
}
}
return instance;
}
}
2.饿汉式
饿汉式主要是基于JVM的类加载机制保证了线程安全
代码如下(示例):
public class HungrySingleton {
//static+final 确定了该引用指向唯一的实例
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
3.静态内部类
静态内部类:
1)本质上也是使用JVM的类加载机制保证线程安全
2)只有在调用getInstance()方法时,才会取初始化静态内部类,并完成该单例类的实例化(类似于懒汉式)
代码如下(示例):
public class InnerClassSingleton {
private static class InnerClassHolder{
private static final InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
4.枚举Enum
Enum:
1)也是使用JVM的类加载器保证线程安全
2)类似于饿汉式
3)可以防止反射创建实例
4)可以防止反序列化对单例的破坏
代码如下(示例):
public enum EnumSingleton {
INSTANCE;
}
四、对单例模式的破坏
1)反射方式创建实例
这种方式破坏,饿汉式和静态内部类可以在构造器内进行判断来防止。
2)反序列化创建实例
可以在单例类内写入readResolve()方法防止创建多个单例。也不能忘记设置序列化版本号。
static final long serialVersionUID = 42L;
Object readResolve() throws ObjectStreamException{
return this.instance;
}
3)Enum类型天然防止了这两种破坏方式。