需要注意的是使用在于 switch 条件进行结合使用时,无需使用 Color 引用。
单例模式可以说是最常使用的设计模式了,它的作用是确保某个类只有一个实例,自行实例化并向整个系统提供这个实例。在实际应用中,线程池、缓存、日志对象、对话框对象常被设计成单例,总之,选择单例模式就是为了避免不一致状态,下面我们将会简单说明单例模式的几种主要编写方式,从而对比出使用枚举实现单例模式的优点。首先看看饿汉式的单例模式:
- /**
- * Created by wuzejian on 2017/5/9.
- * 饿汉式(基于classloder机制避免了多线程的同步问题)
- */
- public class SingletonHungry{
- private staticSingletonHungry instance =newSingletonHungry();private SingletonHungry() {
- }public staticSingletonHungrygetInstance() {returninstance;
- }
- }
显然这种写法比较简单,但问题是无法做到延迟创建对象,事实上如果该单例类涉及资源较多,创建比较耗时间时,我们更希望它可以尽可能地延迟加载,从而减小初始化的负载,于是便有了如下的懒汉式单例:
- /**
- * Created by wuzejian on 2017/5/9..
- * 懒汉式单例模式(适合多线程安全)
- */
- public class SingletonLazy{
- private static volatileSingletonLazy instance;private SingletonLazy() {
- }public static synchronizedSingletonLazygetInstance() {if(instance ==null) {
- instance =newSingletonLazy();
- }returninstance;
- }
- }
这种写法能够在多线程中很好的工作避免同步问题,同时也具备 lazy loading 机制,遗憾的是,由于 synchronized 的存在,效率很低,在单线程的情景下,完全可以去掉 synchronized,为了兼顾效率与性能问题,改进后代码如下:
- public classSingleton {private static volatileSingleton singleton =null;private Singleton(){}public staticSingletongetSingleton(){if(singleton ==null){
- synchronized (Singleton.class){if(singleton ==null){
- singleton =newSingleton();
- }
- }
- }returnsingleton;
- }
- }
这种编写方式被称为 "双重检查锁",主要在 getSingleton() 方法中,进行两次 null 检查。这样可以极大提升并发度,进而提升性能。毕竟在单例中 new 的情况非常少,绝大多数都是可以并行的读操作,因此在加锁前多进行一次 null 检查就可以减少绝大多数的加锁操作,也就提高了执行效率。但是必须注意的是 volatile 关键字,该关键字有两层语义。第一层语义是可见性,可见性是指在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以其它线程会马上读取到已修改的值,关于工作内存和主内存可简单理解为高速缓存(直接与 CPU 打交道)和主存(日常所说的内存条),注意工作内存是线程独享的,主存是线程共享的。volatile 的第二层语义是禁止指令重排序优化,我们写的代码(特别是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同,这在单线程并没什么问题,然而一旦引入多线程环境,这种乱序就可能导致严重问题。volatile 关键字就可以从语义上解决这个问题,值得关注的是 volatile 的禁止指令重排序优化功能在 Java 1.5 后才得以实现,因此 1.5 前的版本仍然是不安全的,即使使用了 volatile 关键字。或许我们可以利用静态内部类来实现更安全的机制,静态内部类单例模式如下:
- /**
- * Created by wuzejian on 2017/5/9.
- * 静态内部类
- */
- public class SingletonInner{
- private static class Holder{
- private staticSingletonInner singleton =newSingletonInner();
- }private SingletonInner(){}public staticSingletonInnergetSingleton(){returnHolder.singleton;
- }
- }
正如上述代码所展示的,我们把 Singleton 实例放到一个静态内部类中,这样可以避免了静态实例在 Singleton 类的加载阶段(类加载过程的其中一个阶段的,此时只创建了 Class 对象,关于 Class 对象可以看博主另外一篇博文, 深入理解 Java 类型信息 (Class 对象) 与反射机制)就创建对象,毕竟静态变量初始化是在 SingletonInner 类初始化时触发的,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的。从上述 4 种单例模式的写法中,似乎也解决了效率与懒加载的问题,但是它们都有两个共同的缺点:
- //测试例子(四种写解决方式雷同)
- public class Singleton implements java.io.Serializable{
- public staticSingleton INSTANCE =newSingleton();protected Singleton() {
- }//反序列时直接返回当前INSTANCE
- privateObjectreadResolve() {returnINSTANCE;
- }
- }
- public staticSingleton INSTANCE =newSingleton();private static volatile booleanflag =true;private Singleton(){if(flag){
- flag =false;
- }else{throw newRuntimeException("The instance already exists !");
- }
- }
如上所述,问题确实也得到了解决,但问题是我们为此付出了不少努力,即添加了不少代码,还应该注意到如果单例类维持了其他对象的状态时还需要使他们成为 transient 的对象,这种就更复杂了,那有没有更简单更高效的呢?当然是有的,那就是枚举单例了,先来看看如何实现:
- /**
- * Created by wuzejian on 2017/5/9.
- * 枚举单利
- */
- public enumSingletonEnum {
- INSTANCE;privateString name;publicStringgetName(){returnname;
- }public void setName(String name){this.name = name;
- }
- }
代码相当简洁,我们也可以像常规类一样编写 enum 类,为其添加变量和方法,访问方式也更简单,使用
进行访问,这样也就避免调用 getInstance 方法,更重要的是使用枚举单例的写法,我们完全不用考虑序列化和反射的问题。枚举序列化是由 jvm 保证的,每一个枚举类型和定义的枚举变量在 JVM 中都是唯一的,在枚举类型的序列化和反序列化上,Java 做了特殊的规定:在序列化时 Java 仅仅是将枚举对象的 name 属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf 方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 等方法,从而保证了枚举实例的唯一性,这里我们不妨再次看看 Enum 类的 valueOf 方法:
- SingletonEnum.INSTANCE
- public static> T valueOf(Class enumType,
- String name) {
- T result = enumType.enumConstantDirectory().get(name);if(result !=null)returnresult;if(name ==null)throw newNullPointerException("Name is null");throw newIllegalArgumentException("No enum constant "+ enumType.getCanonicalName() +"."+ name);
- }
实际上通过调用 enumType(Class 对象的引用) 的 enumConstantDirectory 方法获取到的是一个 Map 集合,在该集合中存放了以枚举 name 为 key 和以枚举实例变量为 value 的 Key&Value 数据,因此通过 name 的值就可以获取到枚举实例,看看 enumConstantDirectory 方法源码:
- Map enumConstantDirectory() {
- if(enumConstantDirectory ==null) {//getEnumConstantsShared最终通过反射调用枚举类的values方法T[] universe = getEnumConstantsShared();if(universe ==null)throw newIllegalArgumentException(
- getName() +" is not an enum type");
- Map m = newHashMap<>(2* universe.length);//map存放了当前enum类的所有枚举实例变量,以name为key值
- for(T constant : universe)
- m.put(((Enum)constant).name(), constant);
- enumConstantDirectory = m;
- }returnenumConstantDirectory;
- }private volatile transientMap enumConstantDirectory = null;
到这里我们也就可以看出枚举序列化确实不会重新创建新实例,jvm 保证了每个枚举实例变量的唯一性。再来看看反射到底能不能创建枚举,下面试图通过反射获取构造器并创建枚举
- public static void main(String[] args) throws IllegalAccessException,
- InvocationTargetException,
- InstantiationException,
- NoSuchMethodException {
- //获取枚举类的构造函数(前面的源码已分析过)
- Constructor constructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class);
- constructor.setAccessible(true);
- //创建枚举
- SingletonEnum singleton = constructor.newInstance("otherInstance", 9);
- }
执行报错
- Exception in thread"main"java.lang.IllegalArgumentException: Cannot reflectively createenumobjects
- at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
- at zejian.SingletonEnum.main(SingletonEnum.java:38)
- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
- at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
- at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
- at java.lang.reflect.Method.invoke(Method.java:498)
- at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
显然告诉我们不能使用反射创建枚举类,这是为什么呢?不妨看看 newInstance 方法源码:
- publicTnewInstance(Object ... initargs)throwsInstantiationException, IllegalAccessException,
- IllegalArgumentException, InvocationTargetException
- {if(!override) {if(!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
- Class caller = Reflection.getCallerClass();
- checkAccess(caller, clazz,null, modifiers);
- }
- }//这里判断Modifier.ENUM是不是枚举修饰符,如果是就抛异常
- if((clazz.getModifiers() & Modifier.ENUM) !=0)throw newIllegalArgumentException("Cannot reflectively create enum objects");
- ConstructorAccessor ca = constructorAccessor;// read volatile
- if(ca ==null) {
- ca = acquireConstructorAccessor();
- }@SuppressWarnings("unchecked")
- T inst = (T) ca.newInstance(initargs);returninst;
- }
源码很了然,确实无法使用反射创建枚举实例,也就是说明了创建枚举实例只有编译器能够做到而已。显然枚举单例模式确实是很不错的选择,因此我们推荐使用它。但是这总不是万能的,对于 android 平台这个可能未必是最好的选择,在 android 开发中,内存优化是个大块头,而使用枚举时占用的内存常常是静态变量的两倍还多,因此 android 官方在内存优化方面给出的建议是尽量避免在 android 中使用 enum。但是不管如何,关于单例,我们总是应该记住:线程安全,延迟加载,序列化与反序列化安全,反射安全是很重重要的。
先思考这样一个问题,现在我们有一堆 size 大小相同而颜色不同的数据,需要统计出每种颜色的数量是多少以便将数据录入仓库,定义如下枚举用于表示颜色 Color:
- enum Color {
- GREEN,
- RED,
- BLUE,
- YELLOW
- }
我们有如下解决方案,使用 Map 集合来统计,key 值作为颜色名称,value 代表衣服数量,如下:
来源: http://blog.csdn.net/javazejian/article/details/71333103