第一次写博客,也是第一篇,从单例模式开始,不足之处,望各位看官海涵。
简介
首先我们都知道单例模式是java常用的23种设计模式之一,它的用途可谓是非常广泛。它的核心就在于单实例,即整个环境中该类有且只能有一个对象。而java创建实例的方式已知的有四种,分别是通过new、clone、反射或者序列化这四种方式去创建实例,怎样保证单例呢,下面且听我一一道来。
单例模式的常见写法:
1.基础饿汉式单例
优点:
类加载时就去初始化,没有线程安全问题,不能通过new创建实例
缺点:
①.能通过反射或者序列化去创建新的实例(解决方式在最后)
②.类加载时就创建好对象,可能会创建出来用不到的实例对象,这样对内存是种浪费
/** * 基础饿汉式单例 */ public class HungrySingleton { private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return hungrySingleton; } }
2.简单懒汉式
优点:
懒汉式和饿汉式不同,它不需要在类加载的时候去创建对象,而是在类实例化的时候才会去创建对象,所以不存在内存空间浪费
缺点:
①.懒汉式是线程不安全的,多个线程同时去实例化该对象有可能会生成多个实例对象,破坏单例
②.能通过反射或者序列化去创建新的实例(解决方式在最后)
/** * 普通的懒汉式 */ public class LazySingleton { private static LazySingleton lazySingleton; private LazySingleton(){} public static LazySingleton getInstance(){ if(lazySingleton == null){ lazySingleton = new LazySingleton(); } return lazySingleton; } }
3.懒汉式二(双重校验锁-double check)
优点:
解决了简单懒汉式的线程安全问题
缺点:
①.加入synchronized关键字,一定程度上影响性能
②.能通过反射或者序列化去创建新的实例(解决方式在最后)
/** * 双重校验锁 */ public class DoubleCheckSingleton { private DoubleCheckSingleton doubleCheckSingleton; private DoubleCheckSingleton(){} public DoubleCheckSingleton getInstance(){ if(doubleCheckSingleton == null){ synchronized (DoubleCheckSingleton.class){ if(doubleCheckSingleton == null){ doubleCheckSingleton = new DoubleCheckSingleton(); } } } return doubleCheckSingleton; } }
4.懒汉式三(静态内部类)
优点:
没有synchronized关键字,不会影响性能,也没有线程安全问题
缺点:
能通过反射或者序列化去创建新的实例(解决方式在最后)
/** * 静态内部类 */ public class StaticInnerClassSingleton implements Serializable { private StaticInnerClassSingleton(){} public static StaticInnerClassSingleton getInstance(){ return InnerClass.staticInnerClassSingleton; } static class InnerClass{ private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } }
5.枚举类型单例
优点:枚举的优点就是没有缺点(我本人没有发现,各位大佬有知道的可以告诉我)
/** * 枚举类型单例 */ public enum EnumSingleton { INSTANCE; public static EnumSingleton getInstance(){ return INSTANCE; } }
反射和序列化破坏单例的解决办法
反射和序列化是破坏单例的两种常见方式,我们在静态内部类的基础上添加解决方案。
首先我们为该类实现Serializable接口,重写readResolve方法,这样能够解决序列化破坏单例的问题;其次,我们在原本空的构造方法中添加一段代码,去判断实例是否已经存在,如果存在则抛异常,这样能够解决反射破坏单例的问题
import java.io.Serializable; /** * 静态内部类 */ public class StaticInnerClassSingleton implements Serializable { private StaticInnerClassSingleton(){ if(InnerClass.staticInnerClassSingleton != null){ throw new RuntimeException("不允许创建多个实例"); } } public static StaticInnerClassSingleton getInstance(){ return InnerClass.staticInnerClassSingleton; } static class InnerClass{ private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } //保证序列化不回破坏单例 private Object readResolve(){ return InnerClass.staticInnerClassSingleton; } }
总结
简单懒汉式是我们极不推荐的一种形式,因为会存在线程安全的问题,双重校验锁和基础饿汉式是通过牺牲一定性能或者空间来达到实现单例的目的,如果性能要求不高或者内存空间足够的话,可以酌情使用。我们更加推荐的形式是静态内部类和枚举类型的单例,尤其是枚举类型,不需要通过额外的代码去防止序列化和反射破坏单例,这是jdk开发者在源码层面就做过限制的,相对而言,静态内部类虽然需要自己手动去做校验,但是它简单易懂,很容易让人理解。