一、并发处理
1.获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
2.创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
3.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。
4.线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor的方式,这养的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Excutors返回的线程池对象的弊端如下:
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Interger.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
- CachedThreadPool:允许的创建线程的数量为Interger.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
5.SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
6.必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。
7.高并发时,同步调用应该去考量锁的性能损耗。能有无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能有对象锁,就不要用类锁。
8.对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
9.在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
10.在使用尝试机制来获取锁的方式中,进去业务代码之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。
11.并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,那么在缓存加锁,要么在数据库使用乐观锁,使用version作为更新依据。
如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。
12.多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其他任务会自动终止运行,使用ScheduleExecutorService则没有这个问题。
13.资金相关的金融敏感信息,使用悲观锁策略。乐观锁在获得锁的同时已经完成了更新操作,校验逻辑容易出现漏洞。
14.避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致的性能下降。
二、控制语句
1.在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
2.当switch括号内的变量类型为String并且此变量为外部参数时,必须先进性null判断。
3.在if/else/for/while/do语句中必须使用大括号。
4.三目运算符condition?表达式1:表示式2中,高度注意表达式1和2在类型对齐时,可能抛出因自动拆箱导致的NPE异常。
以下两种场景会触发类型对齐的拆箱操作:
- 表达式1或表达式2的值只要有一个是原始类型。
- 表达式1或表达式2的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。
5.在高并发场景中,避免使用“等于”判断作为中断或退出的条件。
6.避免采用取反逻辑运算符。
7.公开接口需要进行入参保护,尤其是批量操作的接口。
三、注解规约
1.类、类属性、类方法的注解必须使用Javadoc规范,使用/**内容*/格式,不建议使用// xxx 方式。
2.所有的抽象方法(包括接口中的方法)必须要用Javadoc注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
3.所有的类都必须添加创建者和创建日期。
4.方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注解与代码对齐。
5.所有的枚举类型字段必须要有注解,说明每个数据项的用途。
6.代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
7.在类中删除未使用的任何字段、方法、内部类;在方法中删除未使用的任何参数声明与内部变量。
8.对于注释的要求:
- 能够准确反映设计思想和代码逻辑。
- 能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。
四、前后端规约
1.前后端交互的API,需要明确协议、域名、路径、请求方法、请求内容、状态码、响应体。
说明:
- 协议:生产环境必须使用HTTPS。
- 路径:每一个API需对应一个路径,表示API具体的请求地址,URL路径不要使用大写。
- 请求方法:对具体操作的定义,常见的请求方法如下:
-
- GET:从服务器取出资源。
- POST:在服务器新建一个资源。
- PUT:在服务器更新资源。
- DELETE:从服务器删除资源。
- 请求内容:URL带的参数必须无敏感信息或符合安全要求;body里带参数时必须设置Content-Type。
- 响应体:相应体body可放置多种数据类型,由Content-Type头来确定。
2.前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{}。
3.服务器发生错误时,返回给前端的相应信息必须包含HTTP状态码,errorCode、errorMessage、用户提示信息四部分。
4.在前后端交互的JSON格式数据中,所有的key必须为小写字母开始的lowerCamelCase风格,符合英文表达习惯,且表意完整。
5.errorMessage是前后端错误追踪机制的体现,可以在前端输出到type=“hidden”文字类控件中,或者用户端日志中,帮助我们快速的定位出问题。
6.对于需要使用超大整数的场景,服务端一律使用String字符串类型返回,禁止使用Long类型。
7.HTTP请求通过URL传递参数时,不能超过2048字节。
8.HTTP请求通过body传递内容时,必须控制长度,超出最大长度后,后端解析会出错。
9.在翻页场景中,用户输入参数小于1,则前端返回第一页参数给后端;后端会发现用户输入的参数大于总页数,直接返回最后一页。
10.服务器内部重定向必须使用forward;外部重定向地址必须使用URL统一代理模块生成,否则会因线上采用HTTPS协议而导致浏览器提示“不安全”,并且还会带来URL维护不一致的问题。
11.服务器返回的数据,使用JSON格式而非XML。
12.前后段的时间格式统一为“yyyy-MM-dd HH:mm:ss”,统一为GMT。
五、其他
1.在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
2.避免用Apache Beanutils进行属性的copy。
3.velocity调用POJO类的属性时,直接使用属性名取值即可,模块引擎会自动按规范调用POJO的getXxx(),如果是boolean基本数据类型变量(boolean命名不需要加is前缀),会自动调用isXxx()方法。
4.后台输送给页面的变量必须加$!{var}——中间的感叹号。
5.注意Math.random()整个方法返回是double类型,注意取值的范围 0<=x<1(能够取到零值,注意除零异常),如果想要获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法。