1.定义
单例模式是确保全局只有一个实例,并且提供一个全局的访问点,属于创建型模式,典型应用就是枚举
2.饿汉式单例模式
饿汉式单例模式在类加载的时候就已经创建,属于线程安全模式,因为那个时候还没有线程,在线程出现以前就已经实例化了,代码:
/**
* 优点:执行效率高,性能高,没有任何的锁
* 缺点:某些情况下,可能会造成内存浪费
* create by yufeng on 2021/7/3 21:00
*/
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
/**
* 静态代码块的形式
* create by yufeng on 2021/7/3 21:00
*/
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
3.懒汉式单例模式
为了饿汉模式带来的内存浪费问题,就弄了个懒汉模式,因为只有在被用到的时候才会被实例化,所以就避免了内存浪费的问题
/**
* 优点:节省了内存,线程安全
* 缺点:性能低
* create by yufeng on 2021/7/3 21:12
*/
public class LazySimpleSingletion {
private static LazySimpleSingletion instance;
private LazySimpleSingletion(){}
public synchronized static LazySimpleSingletion getInstance(){
if(instance == null){
instance = new LazySimpleSingletion();
}
return instance;
}
}
为了解决上面性能低的问题,用双重检查锁的方法:
/**
* 优点:性能高了,线程安全了
* 缺点:可读性难度加大,不够优雅
* create by yufeng on 2021/7/3 21:12
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//检查是否要阻塞
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//检查是否要重新创建实例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
//指令重排序的问题
}
}
}
return instance;
}
}
但是第二种写法还是有锁的问题,存在性能的问题,这个时候就用内部类的形式来解决
/*
兼顾了饿汉模式的浪费内存,和懒汉模式锁性能的问题
create by yufeng on 2021/7/3 21:12
*/
public class LazyInnerClassSingleton {
// 使用的时候默认先加载内部类
private LazyInnerClassSingleton(){
}
private static final LazyInnerClassSingleton getInstance(){
return LazyHolder.LAZY;
}
// 默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
但是上面的方法还是可以通过反射进行破坏单例模式,所以在上面的基础实例化前加个校验
/*
ClassPath : LazyStaticInnerClassSingleton.class
LazyStaticInnerClassSingleton$LazyHolder.class
优点:写法优雅,利用了Java本身语法特点,性能高,避免了内存浪费,不能被反射破坏
缺点:不优雅
create by yufeng on 2021/7/3 21:12
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton(){
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
}
4.序列化
序列化和反序列化后,单例模式会被破坏,因为反序列化重新开辟内存重新创建对象,违背了单例的初衷
/**
* create by yufeng on 2021/7/3 21:30
*/
public class SeriableSingleton implements Serializable {
//序列化
//把内存中对象的状态转换为字节码的形式
//把字节码通过IO输出流,写到磁盘上
//永久保存下来,持久化
//反序列化
//将持久化的字节码内容,通过IO输入流读到内存中来
//转化成一个Java对象
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){ return INSTANCE;}
}
通过jdk源码分析我们可以看到readResolve()方法虽然解决了序列化破坏单例的问题,但是实际上是实例化了2次,只不过新创建的对象没有被返回而已,如果该动作频率加快内存分配开销也会加大。注册式模式会解决该问题
5.注册式单例模式
注册式单例模式,也叫登记式单例模式,每一个实例都会登记到某一个地方,使用唯一的标识获取实例,注册式单例模式有2中:枚举、容器
5.1枚举式单例模式
类似饿汉模式,类加载的是就会实例化,也不会被序列化破坏,因为枚举式用过类名和类对象找到唯一对象,所以不能被加载多次,也不会通过反射破坏,因为没无参构造,只有一个protected类型的构造方法,在newInstance() 时有个强制判断,如果是枚举那就直接抛出异常,跟静态内部类类似的处理。
/**
* create by yufeng on 2021/7/3 22:30
*/
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){return INSTANCE;}
}
5.2容器式单例模式
枚举式单例跟饿汉模式差不多,都会加载的时候就进行了实例化,不适合大量创建单例对象的场景,所以就有了容器式单例模式
/**
* create by yufeng on 2021/7/3 22:30
*/
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map ioc = new ConcurrentHashMap();
public static Object getInstance(String className){
Object instance = null;
if(!ioc.containsKey(className)){
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
}catch (Exception e){
e.printStackTrace();
}
return instance;
}else{
return ioc.get(className);
}
}
}
容器式单例模式适用于需要大量创建单例模式对象的场景,但是不是线程安全的
5.源码地址
单例模式源码地址