如前面所述,类初始化是在类真正可用时的最后一项前阶工作,该阶段负责对所有类正确的初始化值,此项工作是线程安全的,JVM会保证多线程同步。
第1步:调用类初始化方法CachingEnumResolver.<clinit>(),该方法对外界是不可见的,换句话说是JVM 内部专用方法,<clinit>()内包括了CachingEnumResolver内所有的具有指定初始值的类变量的初始化语句。要注意的是并非每个类都具有该方法,具体的内容在前面已有叙述。
第2步:进入<clinit>()方法内,让我们看字节码中的“①”行,该行与其上面两行组合起来代表new一个CachingEnumResolver对象实例,而该代码行本身是指调用CachingEnumResolver类的<init>()方法。每一个Java类都具有一个<init>()方法,该方法是Java编译器在编译时生成的,对外界不可见,<init>()方法内包括了所有具有指定初始化值的实例变量初始化语句和java类的构造方法内的所有语句。对象在实例化时,均通过该方法进行初始化。然而到此步,一个潜在的问题已经在此埋伏好,就等着你来犯了。
第3步:让我们顺着执行顺序向下看,“④”行,该行所在方法就是该类的构造器,该方法先调用父类的构造器<init>()对父对象进行初始化,然后调用CachingEnumResolver.initEnum()方法加载数据。
第4步:“⑤”行,该行获取“CODE_MAP_CACHE”字段值,其运行时该字段值为null。注意,问题已经开始显现了。(作为程序员的你一定是希望该字段已经被初始化过了,而事实上它还没有被初始化)。通过判断,由于该字段为NULL,因此程序将继续执行到“⑥”行,将该字段实例化为HashMap()。
第5步:在“⑦”、“⑧”行,其功能就是为“CODE_MAP_CACHE”字段填入两条数据。
第6步:退出对象初始化方法<init>(),将生成的对象实例初始化给类字段“SINGLE_ENUM_RESOLVER”。(注意,此刻该对象实例内的类变量还未初始化完全,刚才由<init>()调用initEnum()方法赋值的类变量“CODE_MAP_CACHE”是<clinit>()方法还未初始化字段,它还将在后面的类初始化过程再次被覆盖)。
第7步:继续执行<clinit>()方法内的后继代码,“②”行,该行对“CODE_MAP_CACHE”字段实例化为HashMap实例(注意:在对象实例化时已经对该字段赋值过了,现在又重新赋值为另一个实例,此刻“CODE_MAP_CACHE”变量所引用的实例的类变量值被覆盖,到此我们的疑问已经有了答案)。
第8步:类初始化完毕,同时该单态类的实例化工作也完成。
通过对上面的字节码执行过程分析,或许你已经清楚了解到导致错误的深层原因了,也或许你可能早已被上面的分析过程给弄得晕头转向了,不过也没折,虽然我也可以从源代码的角度来阐述问题,但这样不够深度,同时也会有仅为个人观点、不足可信之嫌。
如何解决
要解决上面代码所存在的问题很简单,那就是将“SINGLE_ENUM_RESOLVER”变量的初始化赋值语句转移到getInstance()方法中去即可。换句话说就是要避免在类还未初始化完成时从内部实例化该类或在初始化过程中引用还未初始化的字段。
写在最后
静下浮燥之心,仔细思量自己是否真的掌握了本文主题所引出的知识,如果您觉得您已经完全或基本掌握了,那么很好,在最后,我将前面的代码稍做下修改,请思考下面两组程序是否同样会存在问题呢?
程序一
|
程序二
|
最后,一点关于JAVA群体的感言:时下正是各种开源框架盛行时期,Spring更是大行其道,吸引着一大批JEE开发者的眼球(我也是fans中的一员)。然而,让我们仔细观察一下--以 Spring 群体为例,在那么多的Spring fans当中,有多少人去研究过Spring 源代码?又有多少人对Spring 设计思想有真正深入了解呢?当然,我是没有资格以这样的口吻来说事的,我只是想表明一个观点--学东西一定要"正本清源"。
献上此文,谨以共勉。
(责任编辑 火凤凰 sunsj@51cto.com TEL:(010)68476636-8007)
| 共4页: 上一页 [1] [2] [3] 4 | ||
|
|
|||
| · 51CTO主编推荐经典专题 · RAID——磁盘阵列基础 · 充电计划之热门IT认证.. · 51CTO技术自测 挑战自.. · CISSP认证成长之路 · AMD Phenom三核处理器.. · 国际文档格式标准开战 · 2007年互联网大会 |
· 我是黑客我怕谁——讲.. · ARP攻击防范与解决方案 · Solaris 10 配置管理 · Solaris基础知识入门 · RIP路由协议专栏 · MPLS路由协议专栏 · OSPF路由协议专栏 · 思科路由器产品 |
||
|
|||
| · Java基础教程 · VPN技术 · ARP攻击防范与解决方案 · SQL Server 2005全解 · SOA 面向服务架构 · SQL Server 2005全解 · Java编程开发手册 · RAID——磁盘阵列基础 |
· 三层交换技术专题 · SQL Server入门到精通 · Windows Server 2003企.. · Windows远程桌面应用 · C#技术开发指南 · VPN技术 · Solaris 10 配置管理 · C#技术开发指南 |
||
|
|||
| · ARP攻击防范与解决方案 · VPN技术 · SQL Server 2005全解 · Java基础教程 · SQL Server入门到精通 · SQL Server 2005全解 · SOA 面向服务架构 · Java编程开发手册 |
· C#技术开发指南 · 三层交换技术专题 · C#技术开发指南 · Windows远程桌面应用 · RAID——磁盘阵列基础 · Windows Server 2003企.. · 邮件服务器专题 · wimax技术与趋势 |
||
| ·DB2 Viper快速入门 ·DB2 9数据库的镜像分割与.. |
·将XML应用程序从DB2 8.x.. ·DB2 9中的pureXML:如何.. |
| ·服务器中的“傻瓜机”在.. ·盖茨也喜欢登录Youtube看.. |
· · |
| ·拯救系统管理员 ·美国选民:我为什么选布什 |
·VMware公司中文命名挑战赛 ·我们真缺乏创新吗? |
| ·J0ker的CISSP之路:复习-.. ·J0ker的CISSP之路:复习-I.. |
·9月第3周安全回顾 内网安.. ·教你几招识别和防御Web网.. |
| · NGN:下一代网络 · 网络访问中断大排查 · FTTx光纤接入 |
· 教你使用Anti ARP Sniff.. · 网络嗅探教程:使用Snif.. · 常见病毒手工清除方法大.. |
| · C++是垃圾语言?! · 2007年IT界七大抄袭事件 · Java实用开发全集 |
· 解析Ajax开发框架 走进A.. · 基于Google Maps与Ajax.. · 基于Google Maps与Ajax.. |
| · 热门 IT 培训认证官方资.. · Ubuntu 中文开源频道 · Solaris基础知识入门 |
· 费力不讨好 数据中心主.. · AMD Phenom三核处理器解.. · 51CTO主编推荐经典专题 |
| · 甲骨文Oracle 11g正式发.. · Oracle数据库开发之PL/S.. · Oracle数据库开发基础教.. |
· 存储2006,一个并购的大.. · IDC宣布浪潮蝉联存储市.. · 双机热备技术 |