1、单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,2011-5-20,#,代码质量,意识、行为、方法、工具,cantellow,开题,武侠世界里,凡是登峰造极的人,要么是经历了一些奇遇,要么就是老老实实的靠时间积累,要么就练一些歹毒的功夫,吸取别人的武功,但他们的目的都是增强自己的内力,内功心法之所以很重要,那是因为像什么剑法、拳法之类的聪明的人看一遍就会,而内力却很难。,在我们,Java,程序员世界里,也有一些登峰造极的人,比如,James,Gosling,(不解释)、,Joshua,Bloch,(,google,首席,java,架构师,,effective
2、 java,作者)、,Bruce Eckel,(,thinking in java,作者)、,Doug,Lea,(,JDK1.5,并,发包,,Java Concurrency in Practice,作者)、,Kent,Beck,(敏捷之父)、,martin fowler,(重构之父)等等,这些人之所以登峰造极,并不是他们写了多少多少的代码,在于他们每写一行代码就思考,去感悟,为什么这些好,为什么那样写不好,他们把自己悟出的道写成了书,这些书就是所谓的武功秘籍,名扬天下;而且你也可以发现,武侠世界里出名的秘籍大部分都是修炼的内功,神马易筋经啊、九阳真经啊、九阴真经啊,反正离不开一个经字。,程序
3、员的武功秘籍也不例外,神马,Java,编程思想,、,敏捷软件开发,、,设计模式,、,重构,、,领域驱动设计,、,人月神话,、,代码大全,、,程序员修炼之道,、,简单之美,啊神马神马的无不讲的是程序员的首先要修炼好的是内功,今天主题:代码质量和重构就属于内功心法的一种,要练好它,没有几年甚至十年是不行的,这篇,PPT,能做的就是影响你正确的修炼道路,不至于走火入魔。,说明,/,遗憾的地方,代码质量开始,的部分带了个人情感,有点偏离了主题,为此我修改了一下,但是要完全不带个人感情去做这次培训,我是办不到的。,还有人建议,培训,ppt,理论太重了点,,特别是重构,缺乏鲜活的例子,我承认,这跟我预期想
4、的不一样,我开始想把,XXXX,功能的重构过程讲一遍,但是我一看,CC,,发现我已经修改了将近,30,个版本,要唤起那些古老的回忆,我觉得特别头疼,但是要我完全参照,martin fowler,的例子,我又是不太服气的,嘛,如果大家愿意看,我可以直接把,PDF,打开再看看那段例子。,培训的目的是什么,?我自己也经常去想我听了大部分的培训最后得到了什么?自己是否真正的去行动过?如果我要做培训,我要给大家留下什么?带着这些思想,我精心准备着这次,培训(但还是离我的理想太远),,特别是我那份,findbugs,反模式,历时一个月之久,真心希望大家抽点时间看看,。,我,不知道有多少人事前看过,PPT,
5、大家应该知道最后的部分是“困惑的心里没底”,以我目前的视野来看,敏捷思想是解决这个困惑的银弹,由此带来的一系列工具和平台,比如,maven,、,mylyn,、,JIRA,、,scrum,、,sonar,、,hudson,等等都是我想去了解和学习的,其实之前这些都是纳入培训计划的,但是很遗憾,我无法给大家介绍更多的信息,至少现在是如此。,目录,我为什么要投他隐藏,代码质量意识,代码质量和软件质量,代码质量特性,如何提高代码质量,Findbugs,反模式,目录,我为什么要投他隐藏,代码质量意识,代码质量和软件质量,代码质量特性,如何提高代码质量,Findbugs,反模式,别人的观点,public
6、 static boolean validateInteger(String str),try,Integer.parseInt(str);,catch(Exception ex),return false;,return true;,这段代码的功能是什么?我的理解是判断转入字符串是否为整数,那好,判断这个的目的是什么呢?Integer.parseInt(str)的功能是什么,是解析字符串为Int值,而不是判断是不是为整数(虽然可以通过异常来实现这个功能),当然这只是我的理解。我的原则是代码要完全忠实于其目的。对我而言,在写方法的实现中,尽可能做到只专注自已的功能实现。,你的程序没有什么问题,
7、但是,你这个程序存在局限性:,对于一个性能没有要求的地方,无所谓的;,对于性能有要求,但是钱不是问题的地方,无所谓;,如果您刚从事这个行业,对自己没有什么要求,其实程序代码能工作就可以了,也是没有那么多的要求。但是,请不要,误导他人,。,如果你在互联网企业,并且大规模的处理数据,数据又不是那么规整的时候,你就知道你这段代码造成多大的悲剧了。,不懂其实不要紧的,但是又人说你不对的时候,请您好好想下为什么。,你可以看看我的blog写的一些内容。,要看业务场景的。对于桌面程序,小规模程序大部分的处理方式都是对的。但是,也有很多程序员,需要面对严酷的性能,也有很多程序员需要面对严格的代码review,
8、还有些程序员写的是opensource代码,需要面对大伙的挑战。,如果,你引用的opensource代码,都完全象你这么做,你还能相信他们的代码质量么?,写代码有很多“不建议”的方法,“不建议”不是说你不能用,如果不影响的情况下,你也可以用。如果一个程序员经常使用“不建议”的方式做事,那么这习惯就会被你自然的养成。,我当时的观点,我,当时,的语气也,很强烈,现在再回头看比较幼稚。,我为什么要投他隐藏,1.,其实大多数人投他隐藏,都只是出于它的态度问题,我当时也是出于这个角度投他隐藏的。,点评,:既然你能接受得了异常的性能,为什么就接受不了正则的性能?至少正则可以让代码看上去更好看。,我为什么要
9、投他隐藏,2.,可读性、可维护性不好,基于异常的模式降低了代码的清晰度,感觉很暴力,方法里要做的事情尽量和方法名语义保持一致,如果有较大的差异,后来人维护怎么想?,3.,还是从性能说起,建立一个异常对象,是建立一个普通Object耗时的约20倍,而抛出、接住一个异常对象,所花费时间大约是建立异常对象的4倍。这些数据有人专门测试过:,透过JVM看Exception本质,,话说在这篇帖子里,,gdpglc,的态度好多了,呵呵,,JE,水是如此的深。,4.,只是不建议,没有说不能这样做,很固执的一个人,不过固执也不一定都是坏事。,总结:我们很高兴看到不同的思想,但是希望语气更加平和一些,不要搞得好好
10、的讨论一片乌烟瘴气,也不要搞得大张旗鼓误导新人。有争论才有进步,虽然我投了他隐藏,但是心里还是佩服他敢于连发四贴,敢于争论敢于面对大伙挑战的勇气,至少,这种勇气我是没有的,至少,通过争论我们大家都学到了很多东西,至少,比从来不,show,出自己的观点的人更加有益。,目录,我为什么要投他隐藏,代码质量意识,代码质量和软件质量,代码质量特性,如何提高代码质量,Findbugs,反模式,代码质量意识,在接下来我会讲到如何提高代码质量,会讲到一系列措施和工具,比如,codereview,、重构、,findbugs,、敏捷等等,这些东西对代码质量非常有用,但取决你是否行动了,你和你的团队是否具有强烈的代
11、码质量意识,如果没有强烈的代码质量意识,这一切就像是在看我这个小丑在上演一场杯具,过往云烟,看过了就忘记,了,。,所以,代码质量意识很重要,那是你一切行动的源动力,每个人生来就是独一无二的,思想也是独一无二的,但在这方面,我希望大家统一思想:“,XX,出品,必出精品”,大家在心里默念三遍吧。,如何提高代码质量,意识,当你跟别人合租房子时,你会主动把厨房打扫干净吗?如果是你自己的房子呢?,当你要写一份给自己看的心得是,你会把它写得很漂亮很有深度吗?如果早知道这份心得会抄送给全部门看呢?,当你在修复一个,bug,时,你是否会首先确定这个,bug,的来源,如果是自己发现的?能解决就解决,不能解决或者
12、太麻烦就这样吧,反正别人也没发现;如果是,QA,部门发现的?尽量吧,实在不行就推迟解决;如果是客户反映的,而且被领导盯着的?全力以赴,加班加点的干。,当进公司第一天写代码,是否有种要把自己的代码打造成完美的冲动?是不是敲入的第一字符不是代码而是自己的名字?是不是连一个变量名,怎么写注释都要纠结半天?不过一年之后呢?当你团队没有任何质量改进时,你是否还会这样严格要求自己?,本,节同步发表在:,code,很容易,写,good code,就很难,只要有一个人不按规范出牌,规律就会向着破窗效应发展。,目录,我为什么要投他隐藏,代码质量意识,代码质量和软件质量,代码质量特性,如何提高代码质量,Findb
13、ugs,反模式,关于软件质量的思维导图,代码质量,/,软件质量,我们的代码中有一个模块完美地工作了很多年了,只是代码太乱了。我说服了我的老板,我可以重写这个模块,于是我花了三个星期来重写这个模块。今天,我还记得,我的老板站在我的后面看着我,而我在在流着斗大的法汗珠去fix被我重写的“超级漂亮”的那个模块中一个接一个的bug。从那以后,我再也不重写代码了,除非有重大的利益。,点评:这就是所谓的屠宰式编程。这个案例告诉我们两个道理,1)维护代码要用最最最保守的方法来进行。2)重构代码前要像一个商人一样学会计算利益。当然,ThoughtWorks的咨询师一定会告诉你TDD,结对,极限等等方法告诉你如
14、果实践重构。但我想告诉你,一个程序在生产环境里运行好几个年能没有问题是一件很不容易的事,那怕其中的代码再烂,你再看不过去,你都要有一个清醒的头脑明白这几点,1)软件的运行质量是远远大于代码质量的,2)你的测试案例是远远小于生产环境的,3)软件的完美质量,是靠长时间的运行、测试和错误堆出来的,而不是某种方法论。,这是,Javaeye,上面的一篇文章的摘抄,我发表一下我的看法:,“维护代码要用最最最保守的方法来进行”这种说法太绝对,实际情况可能多种多样,不赞同。,“2)你的测试案例是远远小于生产环境的,3)软件的完美质量,是靠长时间的运行、测试和错误堆出来的,而不是某种方法论。”这种说法我比较赞同
15、检测软件的质量,只能依靠长时间的运行和测试,方法论不能用来论证,但是可以作为提高软件质量的一种手段。,“软件的运行质量是远远大于代码质量的”,软件的运行质量是对用户来说的,用户看不到代码质量,如果产品一经发布不在维护,那么这种说法成立。只要还要对用户作出更多的承诺,那么代码质量的好坏直接影响软件的运行质量,开发人员在对,bad code,修改添加功能时,极易出现新的,bug,,组织良好的代码结构,会让维护进行得更加顺利,从而软件质量也能得到更好的保障。,目录,我为什么要投他隐藏,代码质量意识,代码质量和软件质量,代码质量特性,如何提高代码质量,Findbugs,反模式,可维护性,我的心态变化
16、第一次接触编程,学习,C,语言,交换两个数的值:,c=a,a=b,b=c,后来我我从师兄那学到了下面这段代码,觉得写的比我之前的更漂亮:,a=a+b,b=a-b,a=a-b,最后参加工作了,看到很多别人的代码,最后又觉得最漂亮的代码是这样的:,c=a,a=b,b=c,请问大家,为什么我的心态有这样的变化,?,本节同步发表在博客:,to face,交流,2.,关键地方一定要写单元测试,单元测试能够让你更快速回忆当初开发系统时的场景。,3.,利用卡片、白板、或者,excel,(如果你喜欢敏捷,可以试试,scrum,),为你维护的项目或者模块写一个故事,每一个项目,每一个模块,甚至每一行代码都有自
17、己的故事,我们大部分时间都只是它的过客。你为它们建立了历史档案,总有一天你用得着。,4.,你可以写一个小的工具自动化完成相关维护任务(,常规性的,有规律),提高你的工作效率。,5.,养成随时记录思维的好习惯,利用思维导图软件,我用的就是,xmind,。,6.,代码要多些注释,改动代码要写名字和原因,写名字的意思表示个人对此次改动负责,能够提高代码主人翁精神,潜移默化的影响开发人员反复琢磨代码的好坏。,可维护性前提,可理解性,大多数人都能写出计算机可以理解的代码,唯有写出人类容易理解的代码才是优秀的程序员。,其实,维护软件最头痛的是,随着时间的流逝,以前的关于系统的理解和记忆已经逐渐消失了。每次
18、修改bug或增加新功能,要通过看文档,看代码来回忆当时开发系统时的场景。,软件质量特性中最重要的我认为是可维护性,而可维护性的前提是可理解性。,我们要理解谁?,1.,理解人,我们每天的开会,日常交流,任务分配,培训都是在人和人的交流。你是一个理解性强的人,还是一个表达性强的人?这都很重要。,2.,理解文档,信息只有被用心组织才能有更好的可理解性。,3.,理解代码,好的代码会自解释,会说话,坏的代码面目狰狞,五官发育不全,代码的可理解性是靠开发人员来控制的,是造一个天使还是一个怪物,全看我们的态度是怎么样的。,可理解性强的代码不是一门技术,而是一门艺术。,可复用性,为什么,Java,能做大型的应
19、用?就像硬件能在别人的芯片上进行二次开发一样,,Java,也有类似的组件式开发,这些和它的面向对象复用是分不开的,,java,里的复用有两种方式,继承(单继承,但可以用内部类实现多继承的效果)和组合,凡读过,effective java,的人应该都熟悉,martin flower,推荐优先使用组合,我们经常用的最多的,JDK API,,大部分也就是通过组合的方式实现的,比如,JDK API,提供的算法和数据结构,要实现业务逻辑无关性,一般采用的是抽象接口(顶层,Object,提供了强有力的支持)和泛型(,1.5,支持)。,可复用性和封装性息息相关,组合有四种访问限制,,public,、,pro
20、tected,、包权限、,private,,封装性的好坏意味着你对外界做出多少的承诺,我们应该对外做出更少的承诺,只是一个类,一个接口,或者是一个方法,我不明白为什么有那么多人一开始就对外做出更多的承诺,我通常的做法是,能,private,的绝不,protected,,能不,protected,的绝不,public,,一开始就严格封装,即使以后有什么其他需求,我也可用轻易修改权限,不过我承认,要对外提供不可修改的,jar,包,这种控制的确难以取舍。,封装性,对于封装性,我们再来看一个很有趣的例子:,这样写代码是瞎折腾吗?纠结,真是犀利的楼主啊,大家自己,看看,该,怎么,回帖呢?,封装性,我觉得
21、比较靠谱的回复,我们一起学习学习,:,很简单,当你有一天需要加入一下条件或者钩子操作的时候你就知道好处啦。,比如现在你写成public的,所有的地方都直接访问,有一天,你需要在这个属性改变的时候做一些观察者的操作,比如发邮件通知,或者其他操作的时候你就杯具啦,你必须在工程中search到底多少地方调用过,并且在每个后面去加上一段sendEmailIfChange(userid,contents)代码,但是如果是封装了,就直接在set里面判断ifChange然后改了。,哪个,好,你懂的。,这是java的风俗。就好比所有人都在放屁之前脱裤子的社会里,你不脱就是新手。,实际情况确实多数getter/
22、setter都是没有用的。,以OO流派的说法,对象的状态都因该是私有的,对象之间只有message,这就是这个风俗的理论基础。,1.懂OOP的程序员,会告诉你,这样做为了封装.,2.做过3-5年Java企业项目的人,并被客户需求虐待过的同学会告诉你,这样做为了程序易于修改、维护,3.懂模式设计的合格程序员,会告诉你,这样写能扩展成观察者模式,备忘录模式。,4.做过架构设计的牛人,会告诉你,一切为了卸耦.,可扩展性,可扩展性的动力不断变化的用户需求,世界上不变的是变化,软件需求不会变化?不可能!一个系统是否拥抱变化是由它的可维护性和可扩展性决定的,软件环境的变化(可能是业务环境,运行环境)导致软
23、件要进行改动才能满足人们对它的要求,这种系统本身适应变化的能力就是可扩展性。,可扩展性的设计忠告,可扩展性对系统的性能和复杂度都有影响,复杂度很高的,项目很难,维护,亦很难测试,我比较推崇可预见的、简单的扩展设计,愈简单愈好,避免前期大量的设计,对后期的无法预见的扩展,我们可以通过持续的重构来达到目标。所以,可扩展性不是一蹴而就的,是需要随着你对业务领域理解的深入而不断重构获得,一般三次可以达到比较理想的程度。,对于可扩展性的预见就像买股票一样,如果你买中了,你将会受益颇多,如果你没有买中,那么你的代码质量将面临臃肿和沉沦。这对你今后的维护和开发都造成不小的麻烦。,本节同步发表在博客:,els
24、e,每次我在业务跳转的十字路口徘徊时,别人以后开发会不会碰到同样的问题?我真的要在代码里写死,if else,吗?还是我要规划一个继承体系,利用多态性来跳转?或者说是我要提取出业务对象,封装其业务行为?,举,了一个工作中实际的例子,目录,我为什么要投他隐藏,代码质量意识,代码质量和软件质量,代码质量特性,如何提高代码质量,Findbugs,反模式,如何提高代码质量,关注基础,面向对象和设计,模式,等等,基础一定要踏实,我后面会讲到最近做了一份FindBugs反模式,里面有的问题真是啼笑皆非,千奇百怪,至今为止,居然有人实现单例模式,字段竟然是public或者protected的,而且不是一个两
25、个,貌似很多都是一人写了,其他人直接就ctrl+,paste,,从来没有想过为什么,可能你会说,我们的网管不是跑得好好的吗?那你觉得,bug,库里那么多不能复现的,bug,都是测试人员眼睛看花了吗?其中,80%,我估计都是多线程的问题,可能我的语气比较激烈,但是事实确实是这样的,你可以质疑我,但不能质疑事实。,我一直认为,面向对象入门很简单,很多书上都是一些同样的例子,接口、抽象、封装、耦合、继承等等,我们每个人都能多少理解一点,但是像诸如充血模型、贫血模型、领域驱动设计、依赖注入、PO/VO/POJO等等概念(虽然这些经常出现在J2EE项目中,但是也同样是应用于我们的网管开发),我相信在座的
26、很少有人能讲透彻的,包括我自己,当然要达到martin flower的理解深度,没有十年以上的磨练是不可能的。,曾经我花费了一个月的时间去研究设计模式,最后得出的结论让人啼笑皆非:这东西必须要实战,积累足够的项目经验才能深刻理解其中的好处,有句话说得好,经历过才会明白,仅仅想凭几本书就想搞透彻,有点太异想天开了。,如何提高代码质量,重构,其实我们每天都在使用它,重构,是我们的空气和水,只要你的软件还在维护,你就离不开它!,个人觉得,是否积极重构,,,重构质量的好坏,应该,是影响代码质量好坏最重要因素之一,后面将详细介绍它,。,Codereview,再智能的代码检查工具也不能检查出程序中的业务逻
27、辑设计问题,而往往这方面的问题决定着软件质量的高低,所以花一些时间来进行,codereview,很必要,如何高效的进行,codereview,,这可以另开一次培训了,。,方法,学,测试驱动开发(,TDD,),没,写过,不发表,言论,持续集成,XX,是专家,结对编程,没,实践过,不发表,言论,敏捷,正在,学习之中,如何提高代码质量,使用静态代码检查,工具,上面的内容摘自我的博客:,是一个静态分析工具,它检查类或者 JA,R 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。之所以叫做静态分析工具,是因为它在不实际运行程序的情况对软件进行分析。使用findbugs有很多种方式,从 GUI、从命
28、令行、使用 Ant、作为 Eclipse 插件程序和使用 Maven,,甚至作为,hudson,持续集成的插件。,findbugs,自己定义了一系列的,检测器,,,1.3.9,版本的检测器有,83,种,Bad practice,(不好的习惯),,133,种,Correctness,(正确性),,2,种Experimental(实验性问题),,1,种,Internationalization,(国际化问题),,12,种,Malicious code vulnerability,(恶意的代码),,41,种,Multithreaded correctness,(线程问题),,27,种,Perform
29、ance,(性能问题),,9,种Security(安全性问题),,62,种,Dodgy,(狡猾的问题)。,可能,大家更感兴趣的是它的工作原理,我们可以首先看一下,eclipse,插件下面的,lib,包:,其中的,bcel.jar,和,asm-xx.jar,都是对,java,字节码(通过命令,javap -c,类名,反编译,class,文件)的操作,原理都是在类被装载虚拟机之前,动态修改类(可以直接创造类,也就是说不经过,java,编译器那一步),,bcel,是,apache,下面的一个开源项目,而,asm,是由法国某电信公司的研发工程师负责。,Findbugs,反模式,Findbugs,只是一
30、个工具,我们不应该把它变成敲打团队或者个人的政治,武器,。,很多人都说,Findbugs,的黄色臭虫没什么用,其实仔细研究一下,获益匪浅,我刚开始的打算是为,XXX,中的每一个臭虫都写一份解释,但是这个工作量实在是太大,遇到不太熟的臭虫,我自己都要想半天为什么是这样,还要查阅大量的资料,目前只做了将近,50,份解释,不到总数的,1/3,,我在后面列举出,了,一些很重要的而且经常容易出现的臭虫,更详细的资料请参考,Findbugs,反,模式,.docx,。,.used for regular,expression,Bug:,.used for regular expression,Pattern
31、id:,RE_POSSIBLE_UNINTENDED_PATTERN,type:,RE,category:,CORRECTNESS,解释:,String,的,split,方法传递的参数是正则表达式,正则表达式本身用到的字符需要转义,如:句点符号“,.,”,美元符号“,$,”,乘方符号“,”,大括号“,”,方括号“,”,圆括号“,(),”,,竖线“,|,”,星号“,*,”,加号“,+,”,问号“,?,”等等,这些需要在前面加上“,”转义符。,解决方法:,在前面加上“,”转义符。,Comparison of String objects using=or!=,Bug,:,Comparison o
32、f String objects using=or!=,Pattern,id:,ES_COMPARING_STRINGS_WITH_EQ,type:,ES,category:,BAD_PRACTICE,解释:,你确定你已经了解,string,的全部了?,如果你不了解,请参考,FX,大神的博文:,请别再拿“String s=new String(xyz);创建了多少个String实例”来面试了吧,那么,接下来我就开始剥皮了,:,Object,和,StringBuilder,的,toString,方法都是返回一个,new String(),,跟,”,不相等。,如果你之前是这样的定义的:,Strin
33、g name=“”;OK,,它们处于同一个,class,常量池,跟,”,相等。,如果在这之前,你使用了,String.,Intern,方法,你是高手,跟,”,相等。,如果你没有意识到这些问题,却仍然使用,=,和,!=,去比较字符串,那么请不要告诉我是你手滑了,=!,解决方法:,老实使用,equals,方法吧,至少为了保持代码的清晰性。,Invokes AsyncCentral$FireThread.start(),Bug,:,new AsyncCentral()invokes AsyncCentral$FireThread.start(),Pattern,id:,SC_START_IN_CTO
34、R,type:,SC,category:,MT_CORRECTNESS,解释:,构造方法里重启新的线程,我还是第一次见过这样写的。,首先说明三点:,对象,的创建一般分两步走,在堆上,new,对象操作,执行,方法(包含构造方法),为什么我们开发人员看见的只有一步,那是因为,JVM,不想让开发人员在这个过程中插上一脚,破坏对象的初始化流程。,类的加载和初始化是由虚拟机保证同步的,但是对象的生成和初始化就没有任何同步机制来保证了。,构造器不能加,synchronized,,是一项程序语言设计上的选择,(,见,:JLS 8.8.3 Constructor Modifiers),,正常情况下,是不需要加
35、上,synchronized,,但不代表所有的情况都不要加上,synchronized,,更不能认为一个构造器隐含的就是一个,synchronized,。,那什么时候构造方法需要同步呢?通常来说,,方法在生成对象的时候只被执行一次,一般,new,对象的操作可能因为,JVM,自身的关系保证原子性操作(自己臆测的,没有任何根据),所以我们经常不用关心构造方法同步的问题。但是上述情况就不一样了,在构造方法中新启线程,如果,AsyncCentral,是一个状态类,,FireThread,线程极有可能对,AsyncCentral,的状态进行反复读取和写入,更严重的一种情况是,,AsyncCentral,
36、有父类,极有可能在父类的构造方法还没开始前,,FireThread,线程就已经开始执行并对,AsyncCentral,的状态进行“破坏”了。这个时候,就有两个线程来对,AsyncCentral,的状态进行操作了(一个是执行,方法的线程,一个是,FireThread,线程),自然而然,就会存在同步的问题了。,多数时候,我们没有发现,可能是,AsyncCentral,类没有状态,或者是时候未到,我想说的是,我们写的大部分程序都存在同步的问题,本例子就是其中一个,值得我们好好思考。,另一种理解(觉得更靠谱,来自,于,Java.Concurrency.in.Practice,叫做,“对象逃逸”,意思就
37、是说在构造方法里,,this,是可以访问的到的,同一时间,,FireThread,线程而是可以访问到,this,对象的,所以这时候,this,就从,方法线程逃逸到了,FireThread,线程中,这时候初始化就会存在并发问题。,解决方法:,不要再构造方法中新启线程,可以提供,init,方法,其他方法根据实际情况而定。,forces garbage collection,Bug,:,DBExportTask2.exportDBRecords(DBExportProperty,String)forces garbage collection;extremely dubious except in
38、benchmarking code,Pattern,id:,DM_GC,type:,Dm,category:,PERFORMANCE,解释:,有两点:,System.gc(),只是建议,不是命令,,JVM,不能保证立刻执行垃圾回收。,System.gc(),被显示调用时,很大可能会触发,Full GC,。,GC,有两种类型:,Scavenge GC,和,Full GC,,,Scavenge GC,一般是针对年轻代区(,Eden,区)进行,GC,,不会影响老年代和永生代(,PerGen,),由于大部分对象都是从,Eden,区开始的,所以,Scavenge GC,会频繁进行,,GC,算法速度也更快
39、效率更高。但是,Full GC,不同,,Full GC,是对整个堆进行整理,包括,Young,、,Tenured,和,Perm,,所以比,Scavenge GC,要慢,因此应该尽可能减少,Full GC,的次数。,解决方法:,去掉,System.gc(),locked 50%of time,Bug,:,Inconsistent synchronization of URLAlarmMonitor.m_Counter;locked 50%of time,Pattern,id:,IS2_INCONSISTENT_SYNC,type:,IS,category:,MT_CORRECTNESS,解释:
40、m_Counter,只锁住了,50%,,它还是处于线程不安全的状态,如果一个字段只被,read,,那么它是线程安全的,不需要提供额外的同步开销,可以定义为,final,的(参考不可变类的实现),如果既有,read,也有,write,,那么就必须保证每个,get,和,set,方法都同步,而不能像上面一样,只对,set,方法进行了同步。,解决方法:,对,get,和,set,方法都实行同步。,Incorrect lazy initialization of static field,Bug,:,Incorrect lazy initialization of static field TopoCo
41、ntroller.m_This,Pattern,id:,LI_LAZY_INIT_STATIC,type:,LI,category:,MT_CORRECTNESS,解释:,为什么它存在多线程的,bug,,比如线程,1,进入到,if,语句内,被线程,2,打断,线程,2,同样进入了,if,语句内然后生成了一个对象,a,,随即被线程,1,打断,线程,1,又生成了另外一个对象,b,,这还是一个单例么?,更详细的解释请看:,双重检查锁定以及单例模式,另外,关于单例模式更多的资料,参见,单例模式的七种写法,如果你并发功底相当好,请看这篇文章:,用happen-before规则重新审视DCL,解决方法:,我
42、比较钟情于恶汉,如果需要传递参数,我会使用双重校验锁。,makes inefficient use of keySet iterator instead of entrySet iterator,Bug,:,Method JTAMainFrame.initView(JFrame)makes inefficient use of keySet iterator instead of entrySet iterator,Pattern,id:,WMI_WRONG_MAP_ITERATOR,type:,WMI,category:,PERFORMANCE,解释:,很多人都这样遍历,Map,,没错,但是
43、效率很低,先一个一个的把,key,遍历,然后在根据,key,去查找,value,,这不是多此一举么,为什么不遍历,entry,(桶)然后直接从,entry,得到,value,呢?它们的执行效率大概为,1.5:1,(有人实际测试过)。,我们看看,HashMap.get,方法的源代码:,public,Vget(Objectkey),if,(key=,null,),return,getForNullKey();,int,hash=hash(key.hashCode();,for,(Entrye=tableindexFor(hash,table.length);,e!=,null,;,e=e.next
44、),Objectk;,if,(e.hash=hash&(k=e.key)=key|key.equals(k),return,e.value;,return,null,;,从这里可以看出查找,value,的原理,先计算出,hashcode,,然后散列表里取出,entry,,不管是计算,hashcode,,还是执行循环,for,以及执行,equals,方法,都是,CPU,密集运算,非常耗费,CPU,资源,如果对一个比较大的,map,进行遍历,会出现,CPU,迅速飚高的现象,直接影响机器的响应速度,在并发的情况下,简直就是一场灾难。,解决方法:,for,(Map.Entryentry:menuList.entrySet(),mb.add(entry.getValue();,感谢,感谢,JavaEye,社区(虽然现在已改名为,ItEye,了),这里的文章和讨论是我思想的源泉。,感谢各位同事,能给我这次机会做培训,我知道,这里不乏高手的存在,但我更希望看到的是大家能多多的,show,出自己的观点。,






