1、 前言 阿里 巴 巴 Java 开发手册是阿里 巴巴集团 技术团队的集体 智慧 结晶 和 经验总结, 经历了多次大规模一线实战 的 检验及 不断 的 完善, 系统 化地整理成 册 , 反馈 给广大开发 者。 现代 软件行业 的 高速发展对开发 者的综合 素质要求越来越高 , 因为 不仅 是编 程 知识 点 ,其它维度的知识 点 也 会影响到 软件 的 最终交付质量 。比如: 数据库的表结构和 索引设计缺陷可能 带 来 软件 上的架构缺陷或性能风险 ; 工程结构混乱导致 后续维护 艰难 ;没有鉴权 的 漏洞 代码 易 被黑客 攻击等 等。 所以 本 手册以 Java 开发者为中心视角,划 分为编
2、程规约、异常日志 、 单元 测试、 安全规约 、工程结构 、 MySQL 数据 库 六个 维度 ,再根据内容特 征,细分成若干二级子目录。根据约束力强弱及 故障敏感性,规约依次分为强制、推荐、参考三大类。 对于规约 条目 的 延伸信息 中 ,“ 说明 ” 对内容做了 适当 扩展 和解释; “ 正例 ” 提倡 什么样的编码 和 实现 方式 ; “ 反例 ” 说明 需要提防的雷区 , 以及 真实的 错误案例 。 本 手册 的愿景 是 码出高效 , 码出质量 。 现代 软件架构都需要协同 开 发完成,高效协作 即 降低协同成本 , 提升 沟通效率, 所谓无规矩不成方圆,无规范不能协作。众所周知,制订
3、交通法规表面上是要限制行 车权,实际上是保障公众的人身安全。试想如果没有限速,没有红绿灯 ,谁还敢上路行驶。对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的 统一 方式一起做事,提升协作效率。 代码 的字里行间 流淌的 是 软件 生命中的血液, 质量 的提升是尽 可能少踩坑 ,杜绝 踩重复的坑, 切实 提升质量意识 。 考虑到可以零距离地与众多开发同学进行互动,决定在线维护 手册内容, 此版本号为 1.3.0的 PDF 版本 , 是对外释放的终 极版 ; 其次 , 我们会 在 2017 年 10 月 14日杭州 云栖 大会 上 , 进行 阿里
4、巴巴 Java 开发规约插件全球首发 , 插件 点此下载 , 阿 里巴 巴 云 效 (一站式企业协同研发 云 ) 也会集成 代码 规约 扫描引擎 。 最 后, 码出高效 阿里巴巴 Java 开发手册详解 即将 出版,敬请关注。 目录 前言 一 、 编程规约 . 1 (一 ) 命名风格 . 1 (二 ) 常量定义 . 3 (三 ) 代码格式 . 4 (四 ) OOP规约 . 6 (五 ) 集合处理 . 9 (六 ) 并发处理 12 (七 ) 控制语句 14 (八 ) 注释规约 16 (九 ) 其它 17 二、异常日志 18 (一 ) 异常处理 18 (二 ) 日志规约 19 三、单元测试 21
5、四、安全规约 23 五、 MySQL数据库 24 (一 ) 建表规约 24 (二 ) 索引规约 25 (三 ) SQL语句 27 (四 ) ORM映射 28 六、工程结构 30 (一 ) 应用分层 30 (二 ) 二方库依赖 31 (三 ) 服务器 32 附 1:版本历史 . 34 附 2:本手册专有名词 . 35 (注 : 浏览 时请使用 PDF左侧导航栏) 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 1 /35 Java 开发 手册 版本号 制定团队 更新日期 备注 1.3.0 阿 里巴巴 集团 技术团队 2017.9.25 增加 单元测试规约( PDF终 极版) 一 、 编程
6、规约 (一 ) 命名 风格 1. 【强制】 代码中的 命名均不能以 下划线或美元符号 开始,也不能以 下划线或美元符号 结束。 反例: _name / _name / $Object / name_ / name$ / Object$ 2. 【强制】 代码中的 命名 严禁使用拼音 与英文混合的方式,更不允许直接使用中文的方式。 说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用。 正例: alibaba / taobao / youku / hangzhou 等国际通用的名称, 可 视 同 英文。 反例: DaZhePromotion 打折 / g
7、etPingfenByName() 评分 / int 某 变量 = 3 3. 【强制】类名使用 UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外: DO / BO / DTO / VO / AO 正例: MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion 反例: macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion 4. 【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase风格,必须遵从驼峰形式。 正例: localV
8、alue / getHttpMessage() / inputUserId 5. 【 强制】常量命名 全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。 正例: MAX_STOCK_COUNT 反例: MAX_COUNT 6. 【强制】抽象类命名使用 Abstract或 Base开头 ; 异常类命名使用 Exception结尾 ; 测试类命名以它要测试的类的名称开始,以 Test结尾。 7. 【强制】中括号是数组类型的一部分,数组定义如下: String args; 反例: 使用 String args的方式来 定义 。 8. 【强制】 POJO类中 布尔类型的变量,都不要加
9、is,否则部分框架解析会引起序列化错误。 反例: 定义为基本数据类型 Boolean isDeleted; 的属性,它的方法也是 isDeleted(), RPC阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 2 /35 框架在反向解析的时候, “以为 ”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。 9. 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数 形式,但是类名如果有复数含义,类名可以使用复数形式。 正例: 应用工具类包名为 com.alibaba.open.util、类名为 MessageUtils( 此规则参考sp
10、ring的框架结构 ) 10. 【强制】杜绝完全不规范的缩写, 避免望文不知义 。 反例: AbstractClass“ 缩写 ” 命名成 AbsClass; condition“ 缩写 ” 命名成 condi,此类随意缩写严重降低了代码的可阅读性。 11. 【推荐】为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。 正例: 从 远程 仓库 拉取代码的类 命名 为 PullCodeFromRemoteRepository。 反例: 变量 int a; 的 随 意命名 方式。 12. 【推荐】如果 模块 、 接口 、类、方法使用了设计模式,在 命名时 体现出
11、具体模式。 说明: 将设计模式体现在名字中,有利于阅读者快速理解架构设计 理念 。 正例: public class OrderFactory; public class LoginProxy; public class ResourceObserver; 13. 【推荐】接口类中的方法和属性不要加任何修饰符号 ( public 也不要加 ) ,保持代码的简洁性,并加上有效的 Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。 正例: 接口方法签名: void f(); 接口基础常量表示: String COMPANY = “al
12、ibaba“; 反例: 接口方法定义: public abstract void f(); 说明: JDK8中接口允许有默认实现,那么这个 default方法,是对所有实现类都有价值的默认实现。 14. 接口和实现类的命名有两套规则: 1) 【强制】对于 Service和 DAO类,基于 SOA的理念,暴露出来的服务一定是接口,内部的实现类用 Impl的后缀与接口区别。 正例: CacheServiceImpl实现 CacheService接口。 2) 【推荐】 如果是形容能力的接口名称,取对应的形容词做接口名 ( 通常是 able的形式 ) 。 正例: AbstractTranslator实
13、现 Translatable。 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 3 /35 15. 【参考】枚举类名建议带上 Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。 说明: 枚举其实就是特殊的常量类,且构造方法被默认强制是私有。 正例: 枚举名字 为 ProcessStatusEnum的 成员名称 : SUCCESS / UNKOWN_REASON。 16. 【参考】各层命名规约: A) Service/DAO层方法命名规约 1) 获取单个对象的方法用 get做前缀。 2) 获取多个对象的方法用 list做前缀。 3) 获取统计值的方法用 count做前缀。 4)
14、插入的方法 用 save/insert做前缀。 5) 删除的方法 用 remove/delete做前缀。 6) 修改的方法 用 update做前缀。 B) 领域模型命名规约 1) 数据对象: xxxDO, xxx即为数据表名。 2) 数据传输对象: xxxDTO, xxx为业务领域相关的名称。 3) 展示对象: xxxVO, xxx一般为网页名称。 4) POJO是 DO/DTO/BO/VO的统称,禁止命名成 xxxPOJO。 (二 ) 常量定义 1. 【强制】不允许 任何魔法值 ( 即未经定义的常量 ) 直接出现在代码中。 反例: String key = “Id#taobao_“ + tr
15、adeId; cache.put(key, value); 2. 【强制】 long或者 Long初始赋值时, 使用大写的 L,不能是小写的 l,小写容易跟数字 1混淆,造成误解。 说明: Long a = 2l; 写的是数字的 21,还是 Long型的 2? 3. 【推荐】不要使 用一个常量类维护所有常量, 按常量功能进行归类,分开维护。 说明: 大而全的常量类,非得 使用查找功能 才 能 定位到修改的常量,不利于理解 和 维护。 正例: 缓存相关常量放在类 CacheConsts下 ; 系统配置相关常量放在类 ConfigConsts下。 4. 【推荐】常量的复用层次有五层:跨应用共享常量
16、、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。 1) 跨应用共享常量:放置在二方库中,通常是 client.jar中的 constant目录下。 2) 应用内共享常量:放置在一方库 中 , 通常 是 modules中的 constant目录下。 反例: 易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示“是 ”的变量: 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 4 /35 类 A中: public static final String YES = “yes“; 类 B中: public static final String YES =
17、“y“; A.YES.equals(B.YES),预期是 true,但实际返回为 false,导致 线上问题。 3) 子工程内部共享常量:即在当前子工程的 constant目录下。 4) 包内共享常量:即在当前包下单独的 constant目录下。 5) 类内共享常量:直接在类内部 private static final定义。 5. 【推荐】如果 变量 值 仅在一个范围内变化 ,且 带有名称之外的延伸属性 , 定 义为 枚举 类。下面正例中的数字就是延伸信息,表示星期几。 正例: public Enum MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY
18、(4), FRIDAY(5), SATURDAY(6), SUNDAY(7); (三 ) 代码 格式 1. 【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成 即可,不需要换行 ; 如果是非空代码块则: 1) 左大括号前不换行。 2) 左大括号后换行。 3) 右大括号前换行。 4) 右大括号后还有 else等代码则不换行 ; 表示终止 的 右大括号后必须换行。 2. 【强制】 左 小 括号和 字符之间不出现空格 ; 同样,右 小 括号和字符之间也不出现空格。详见第 5条下方正例提示。 反例: if (空格 a = b 空格 ) 3. 【强制】 if/for/while/switch/d
19、o等保留字与 括号之间都必须加空格。 4. 【强制】任何 二 目 、 三目 运算符 的 左右 两边 都需要 加一个空格。 说明: 运算符包括赋值运算符 =、逻辑运算符 / 运算符的左右必须有一个空格 int flag = 0; 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 5 /35 / 关键词 if与括号之间必须有一个空格,括号内 的 f与左括号, 0与右括号不需要空格 if (flag = 0) System.out.println(say); / 左大括号前加空格且不换行;左大括号后换行 if (flag = 1) System.out.println(“world“); /
20、右大括号前换行,右大括号后有 else,不用换行 else System.out.println(“ok“); / 在右大括号后直接结束,则必须换行 6. 【强制】 注释的 双斜线与 注释 内容之间 有 且仅有 一个空格 。 正例: / 注释 内容 , 注意 在 /和 注释内容 之间有一个空格 。 7. 【强制】单行字符数限制不超过 120 个,超出需要换行,换行时 遵循如下原则: 1) 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。 2) 运算符与下文一起换行。 3) 方法调用的点符号与下文一起换行。 4) 方法 调用时,多个参数 , 需要 换行时, 在 逗号后 进
21、行 。 5) 在括号前不要换行,见反例。 正例: StringBuffer sb = new StringBuffer(); / 超过 120个字符的情况下,换行缩进 4个空格, 点 号 和 方法名称 一起换行 sb.append(“zi“).append(“xin“). .append(“huang“). .append(“huang“). .append(“huang“); 反例: StringBuffer sb = new StringBuffer(); / 超过 120个字符的情况下,不要在 括号前 换行 sb.append(“zi“).append(“xin“).append (“h
22、uang“); / 参数很多的方法调用 可能 超过 120个字符, 不要 在 逗号前 换行 method(args1, args2, args3, . , argsX); 8. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。 正例: 下例中实参的 “a“,后边必须要有一个空格。 method(“a“, “b“, “c“); 9. 【强制】 IDE的 text file encoding设置为 UTF-8; IDE中文件的换行符使用 Unix格式,不要使用 Windows格式。 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 6 /35 10. 【推荐】没有必要增加若干空格
23、来使某一行的字符与上一行 对应位置 的字符 对齐。 正例: int a = 3; long b = 4L; float c = 5F; StringBuffer sb = new StringBuffer(); 说明: 增加 sb这个变量,如果需要对齐,则给 a、 b、 c都要增加几个空格,在变量比较多的情况下,是一种累赘的事情。 11. 【推荐】方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。 说明: 没有必要插入 多 个 空行 进行隔开。 (四 ) OOP 规约 1. 【强制】避免通过一个类的对象引用访问此类的
24、静态变量或静态方法,无谓增加编译器解析成本,直接用 类名 来访问即可。 2. 【强制】所有的覆写方法,必须加 Override注解。 说明: getObject()与 get0bject()的问题。一个是字母的 O,一个是数字的 0,加 Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。 3. 【强制】相同参数类型,相同业务含义,才可以使用 Java的可变参数,避免使用 Object。 说明: 可变参数必须放置在参数列表的最后。 ( 提倡同学们尽量不用可变参数编程 ) 正例: public User getUsers(String typ
25、e, Integer. ids) . 4. 【强制】外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加 Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。 5. 【强制】不能使用过时的类或方法。 说明: .URLDecoder 中的方法 decode(String encodeStr) 这个方法已经过时,应该使用双参数 decode(String source, String encode)。接口提供方既然明确是过时接口,那么有义务同时提供新的接口 ; 作为调用方来说,有义务去考证过时方法的新实现是什么。 6. 【强制】 Objec
26、t的 equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。 正例: “test“.equals(object); 反例: object.equals(“test“); 说明: 推荐使用 java.util.Objects#equals( JDK7引入的工具类 ) 7. 【强制】所有的相同类型的包装类对象之间 值的比较 ,全部使用 equals方法比较。 说明: 对于 Integer var = ? 在 -128至 127范围 内 的赋值, Integer对象是在阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 7 /35 IntegerCache.cache产
27、生,会复用已有对象,这个区间内的 Integer值可以直接使用 =进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals方法进行判断。 8. 关于基本数据类型与包装数据类型的使用标准如下: 1) 【强制】 所有的 POJO类属性必须使用包装数据类型。 2) 【强制】 RPC方法的返回值和参数必须使用包装数据类型。 3) 【 推荐 】 所有的局部变量使用基本数据类型。 说明: POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。 正例: 数据库的查询结果可能是 null,因
28、为自动拆箱,用基本数据类型接收有 NPE风险。 反例: 比如 显示成交总额涨跌情况,即正负 x%, x为基本数据类型,调用的 RPC服务,调用不成功时,返回的是默认值,页面显示 为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null值,能够表示额外的信息,如:远程调用失败,异常退出。 9. 【强制】定义 DO/DTO/VO等 POJO类时,不要设定任何属性 默认值 。 反例: POJO类 的 gmtCreate默认值为 new Date();但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。 10. 【强制】序列化类新增
29、属性时,请不要修改 serialVersionUID字段,避免反序列失败 ; 如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID值。 说明: 注意 serialVersionUID不一致会抛出序列化运行时异常。 11. 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init方法中。 12. 【强制】 POJO类必须写 toString方法。使用 IDE的 中工具 : source generate toString时,如果继承了另一个 POJO类,注意在前面加一下 super.toString。 说明: 在方法执行抛出异常时,可以直接调用
30、POJO的 toString()方法打印其属性值,便于排查问题。 13. 【推荐】使用索引访问用 String的 split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException的风险。 说明: String str = “a,b,c,“; String ary = str.split(“,“); / 预期大于 3,结果是 3 System.out.println(ary.length); 14. 【推荐】当一个类有多个构造 方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读 ,此 条 规则 优先 于 第 15条 规则。
31、 15. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 私有方法 getter/setter方法。 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 8 /35 说明: 公有方法是类的调用者和维护者最关心的方法,首屏展示最好 ; 保护方法虽然只是子类关心,也可能是 “模板设计模式 ”下的核心方法 ; 而私有方法外部一般不需要特别关心,是一个黑盒实现 ; 因为 承载 的 信息价值较低,所有 Service和 DAO的 getter/setter方法放在类体最后。 16. 【推荐】 setter方法中,参数名称与类成员变量名称一致, this.成员名 = 参数名。在getter/se
32、tter方法中, 不要增加业务逻辑,增加排查问题 的 难度。 反例: public Integer getData() if (true) return this.data + 100; else return this.data - 100; 17. 【推荐】循环体内,字符串的 连 接方式,使用 StringBuilder的 append方法进行扩展。 说明: 反编译出的字节码文件显示每次循环都会 new出一个 StringBuilder对象,然后进行append操作,最后通过 toString方法返回 String对象,造成内存资源浪费。 反例: String str = “start“;
33、 for (int i = 0; i list = new ArrayList(2); list.add(“guan“); list.add(“bao“); String array = new Stringlist.size(); array = list.toArray(array); 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 10 /35 反例: 直接使用 toArray无参方法存在问题,此方法返回值只能是 Object类,若强转其它类型数组将出现 ClassCastException错误。 5. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使
34、用其修改集合相关的方法,它的 add/remove/clear方法会抛出 UnsupportedOperationException异常。 说明: asList的返回对象是一个 Arrays内部类,并没有实现集合的修改方法。 Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。 String str = new String “you“, “wu“ ; List list = Arrays.asList(str); 第一种情况: list.add(“yangguanbao“); 运行时异常。 第二种情况: str0 = “gujin“; 那么 list.get(0)
35、也会随之修改。 6. 【强制】泛型通配符 来接收返回的数据,此写法的泛型集合不能使用 add方法 , 而 不 能使用 get方法,做 为 接口 调用 赋 值时易出错。 说明: 扩展 说一下 PECS(Producer Extends Consumer Super)原则: 第 一、 频繁往外读取内容的,适合用 。 第 二、 经常往里插入的,适合用 。 7. 【强制】不要在 foreach循环里进行元素的 remove/add操作。 remove元素请使用 Iterator方式,如果并发操作,需要对 Iterator对象加锁。 正例: Iterator iterator = list.iterat
36、or(); while (iterator.hasNext() String item = iterator.next(); if (删除元素的条件 ) iterator.remove(); 反例: List list = new ArrayList(); list.add(“1“); list.add(“2“); for (String item : list) if (“1“.equals(item) list.remove(item); 说明: 以上代码的 执行结果 肯定 会出乎大家的意料,那么试一下把 “1”换成 “2”,会是同样的结果吗? 8. 【强制】 在 JDK7版本 及 以上,
37、 Comparator要满足 如下 三个条件 ,不然 Arrays.sort,Collections.sort会报 IllegalArgumentException异常。 说明: 三个条件 如 下 1) x, y的比较结果和 y, x的比较结果相反。 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 11 /35 2) xy, yz, 则 xz。 3) x=y, 则 x, z比较结果和 y, z比较结果相同。 反例: 下例中没有处理相等的情况,实际使用中可能会出现异常: new Comparator() Override public int compare(Student o1, St
38、udent o2) return o1.getId() o2.getId() ? 1 : -1; ; 9. 【推荐】集合初始化时, 指定集合初始值大小。 说明: HashMap使用 HashMap(int initialCapacity) 初始化 , 正 例: initialCapacity = (需要 存储的元素个数 / 负载因子 ) + 1。 注意 负载因子 ( 即 loader factor) 默认为 0.75, 如果 暂时无法 确定 初始值大小,请设置为 16(即 默认值) 。 反例: HashMap需要 放置 1024个 元素, 由于 没有设置容量 初 始大小,随着元素不断增加,容量
39、 7 次 被迫扩大, resize需要重建 hash表,严重影响性能。 10. 【推荐】使用 entrySet遍历 Map类集合 KV,而不是 keySet方式进行遍历。 说明: keySet其实是遍历了 2次,一次是转为 Iterator对象,另一次是从 hashMap中取出key所对应的 value。而 entrySet只是遍历了一次就把 key和 value都放到了 entry中,效率更高。如果是 JDK8,使用 Map.foreach方法。 正例: values()返回的是 V值集合,是一个 list集合对象 ; keySet()返回的是 K值集合,是一个 Set集合对象 ; entr
40、ySet()返回的是 K-V值组合集合。 11. 【推荐】高度注意 Map类集合 K/V能不能存储 null值的情况,如下表格: 集合类 Key Value Super 说明 Hashtable 不允许为 null 不允许为 null Dictionary 线程安全 ConcurrentHashMap 不允许为 null 不允许为 null AbstractMap 锁 分段 技术 ( JDK8:CAS) TreeMap 不允许为 null 允许为 null AbstractMap 线程不安全 HashMap 允许为 null 允许为 null AbstractMap 线程不安全 反例: 由于
41、HashMap 的干扰,很多人认为 ConcurrentHashMap是 可以置入 null值 ,而 事实上,存储 null值时会抛出 NPE异常 。 12. 【参考】合理利用好集合的有序性 (sort)和稳定性 (order),避免集合的无序性 (unsort)和不稳定性 (unorder)带来的负面影响。 说明: 有序性是指遍历的结果是按某种比较规则依次排列的。 稳定性指集合每次遍历的元素次序是一定的。 如: ArrayList是 order/unsort; HashMap是 unorder/unsort; TreeSet是order/sort。 阿里巴巴 Java开发手册 禁止用于商业用
42、途,违者必究 12 /35 13. 【参考】利用 Set元素唯一的特性,可以快速对 一个集合进行去重操作,避免使用 List的contains方法进行遍历 、对比、 去重操作。 (六 ) 并发处理 1. 【强制 】 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。 说明: 资源驱动类、工具类、单例工厂类都需要注意。 2. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 正例: public class TimerTaskThread extends Thread public TimerTaskThread() super.setName(“TimerTaskTh
43、read“); . 3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明: 使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换 ”的问题。 4. 【强制】线程池不允许使用 Executors去创建,而是通过 ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明: Executors返回的线程池对象 的弊端 如下 : 1) FixedThreadPool和 SingleThreadPo
44、ol: 允许的请求队列长度为 Integer.MAX_VALUE,可 能会堆积大量的请求,从而导致 OOM。 2) CachedThreadPool和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。 5. 【强制】 SimpleDateFormat 是线程不安全的类,一般不要定义为 static变量,如果定义为static,必须加锁,或者使用 DateUtils工具类。 正例: 注意线程安全,使用 DateUtils。亦推荐如下处理: private static final ThreadLocal
45、 df = new ThreadLocal() Override protected DateFormat initialValue() return new SimpleDateFormat(“yyyy-MM-dd“); ; 说明: 如果是 JDK8的应用,可以使用 Instant代替 Date, LocalDateTime代替 Calendar,DateTimeFormatter代替 SimpleDateFormat,官方给出的解释: simple beautiful strong immutable thread-safe。 阿里巴巴 Java开发手册 禁止用于商业用途,违者必究 13
46、/35 6. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁 ; 能锁区块,就不要锁整个方法体 ; 能用对象锁,就不要用类锁。 说明: 尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。 7. 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。 说明: 线程一需要对表 A、 B、 C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、 B、 C,否则可能出现死锁。 8. 【强制】并发修改同一记录时,避免更新丢失, 需要 加锁。 要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观
47、锁,使用 version作为更新依据。 说明: 如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3次。 9. 【强制】多线程并行处理定时任务时, Timer运行 多个 TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService则没有这个问题。 10. 【推荐】使用 CountDownLatch进行异步转同步操作,每个线程退出前必须调用 countDown方法,线程执行代码注意 catch异常,确保 countDown方法 被 执行 到 ,避免主线程无法执行至 await方法,直到超时才返回结果。 说明: 注意,子线程抛出异常堆栈,不能在主线程 try-catch到。 11. 【推荐】避免 Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降。 说明: Random实例包括 java.util.Random 的实例或者 Math.random()的 方式 。 正例: 在 JDK7之后,可以直接使用 API ThreadLocalRandom, 而 在 JDK7之前, 需要 编码保证 每个线程 持 有 一个实例。 12. 【推荐】 在并发场景 下 , 通过双重检查锁 ( double-checked lockin