java枚举是如何保证线程安全的
前言
写在前面:JavaSE5提供了一种新的类型-Java的枚举类型,关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。本文将深入分析枚举的源码,看一看枚举是怎么实现的,他是如何保证线程安全的,以及为什么用枚举实现的单例是最好的方式。
枚举是如何保证线程安全的
要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是enum吗?答案很明显不是,enum就和class一样,只是一个关键字,他并不是一个类,那么枚举是由什么类维护的呢,我们简单的写一个枚举:
publicenumt{ SPRING,SUMMER,AUTUMN,WINTER; }
然后我们使用反编译,看看这段代码到底是怎么实现的,反编译(Java的反编译)后代码内容如下:
publicfinalclassTextendsEnum { privateT(Strings,inti) { super(s,i); } publicstaticT[]values() { Tat[]; inti; Tat1[]; System.arraycopy(at=ENUM$VALUES,0,at1=newT[i=at.length],0,i); returnat1; } publicstaticTvalueOf(Strings) { return(T)Enum.valueOf(demo/T,s); } publicstaticfinalTSPRING; publicstaticfinalTSUMMER; publicstaticfinalTAUTUMN; publicstaticfinalTWINTER; privatestaticfinalTENUM$VALUES[]; static { SPRING=newT("SPRING",0); SUMMER=newT("SUMMER",1); AUTUMN=newT("AUTUMN",2); WINTER=newT("WINTER",3); ENUM$VALUES=(newT[]{ SPRING,SUMMER,AUTUMN,WINTER }); } }
通过反编译后代码我们可以看到,publicfinalclassTextendsEnum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,我们看到这个类中有几个属性和方法。
我们可以看到:
publicstaticfinalTSPRING; publicstaticfinalTSUMMER; publicstaticfinalTAUTUMN; publicstaticfinalTWINTER; privatestaticfinalTENUM$VALUES[]; static { SPRING=newT("SPRING",0); SUMMER=newT("SUMMER",1); AUTUMN=newT("AUTUMN",2); WINTER=newT("WINTER",3); ENUM$VALUES=(newT[]{ SPRING,SUMMER,AUTUMN,WINTER }); }
都是static类型的,因为static类型的属性会在类被加载之后被初始化,我们在深度分析Java的ClassLoader机制(源码级别)和Java类的加载、链接和初始化两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。
为什么用枚举实现的单例是最好的方式
在[转+注]单例模式的七种写法中,我们看到一共有七种实现单例的方式,其中,EffectiveJava作者JoshBloch提倡使用枚举的方式,既然大神说这种方式好,那我们就要知道它为什么好?
1.枚举写法简单
写法简单这个大家看看[转+注]单例模式的七种写法里面的实现就知道区别了。
publicenumEasySingleton{ INSTANCE; }
你可以通过EasySingleton.INSTANCE来访问。
2.枚举自己处理序列化
我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。
但是,**为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。原文如下:
Enumconstantsareserializeddifferentlythanordinaryserializableorexternalizableobjects.Theserializedformofanenumconstantconsistssolelyofitsname;fieldvaluesoftheconstantarenotpresentintheform.Toserializeanenumconstant,ObjectOutputStreamwritesthevaluereturnedbytheenumconstant'snamemethod.Todeserializeanenumconstant,ObjectInputStreamreadstheconstantnamefromthestream;thedeserializedconstantisthenobtainedbycallingthejava.lang.Enum.valueOfmethod,passingtheconstant'senumtypealongwiththereceivedconstantnameasarguments.Likeotherserializableorexternalizableobjects,enumconstantscanfunctionasthetargetsofbackreferencesappearingsubsequentlyintheserializationstream.Theprocessbywhichenumconstantsareserializedcannotbecustomized:anyclass-specificwriteObject,readObject,readObjectNoData,writeReplace,andreadResolvemethodsdefinedbyenumtypesareignoredduringserializationanddeserialization.Similarly,anyserialPersistentFieldsorserialVersionUIDfielddeclarationsarealsoignored--allenumtypeshaveafixedserialVersionUIDof0L.Documentingserializablefieldsanddataforenumtypesisunnecessary,sincethereisnovariationinthetypeofdatasent.
大概意思就是说,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。
同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。我们看一下这个valueOf方法:
publicstatic>TvalueOf(Class enumType,Stringname){ Tresult=enumType.enumConstantDirectory().get(name); if(result!=null) returnresult; if(name==null) thrownewNullPointerException("Nameisnull"); thrownewIllegalArgumentException( "Noenumconst"+enumType+"."+name); }
从代码中可以看到,代码会尝试从调用enumType这个Class对象的enumConstantDirectory()方法返回的map中获取名字为name的枚举对象,如果不存在就会抛出异常。
再进一步跟到enumConstantDirectory()方法,就会发现到最后会以反射的方式调用enumType这个类型的values()静态方法,也就是上面我们看到的编译器为我们创建的那个方法,然后用返回结果填充enumType这个Class对象中的enumConstantDirectory属性。
所以,JVM对序列化有保证。
3.枚举实例创建是thread-safe(线程安全的)
当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。