日常编程中的小技巧以及注意点(三)

    之前写过一篇《日常编程中的小技巧以及注意点(二)》,感兴趣可以查看。


<1> SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类

    正例:注意线程安全,使用DateUtils。推荐如下处理。

    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
​
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    说明:如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。


<2> 在高并发场景中,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁

    说明:使加锁的代码块工作量尽可能小,避免在锁代码块中调用RPC方法。


<3> 在对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁

说明:如果线程一需要对表A、B、C依次全部加锁后才可以进行更新操作,那么线程二的的加锁顺序也必须是A、B、C,否则可能出现死锁。


<4> 在并发修改同一记录时,为避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存层加锁,要么在数据库层使用乐观锁,使用version作为更新依据

说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。


<5> 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致性能下降

说明:Random实例包括java.util.Random的实例或者Math.random()的方式。

正例:在JDK7之后,可以直接使用API ThreadLocalRandom,而在JDK7之前,需要编码保证每个线程持有一个实例。


<6> volatile解决多线程内存不可见问题

    对于一写多读,可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是count++操作,使用如下类实现:

        AtomicLong count = new AtomicLong();
        count.addAndGet(1);

    如果是JDK8,推荐使用LongAdder对象,它比AtomicLong性能更好(减少乐观锁的重试次数)。缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。


<7> HashMap在容量不够进行resize时,由于高并发可能出现死链,导致CPU占用飙升,在开发过程中可以使用其他数据结构或加锁来规避此风险


<8> 在高并发场景中,避免使用“等于”判断作为中断或退出的条件

说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,应使用大于或小于的区间判断条件来代替。

反例:判断剩余奖品数量等于0时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。


<9> 在修改代码的同时,要对注释进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改

    说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件更新严重滞后,就失去了导航的意义。


<10> 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度

说明:不要在方法体内定义:Pattern pattern = Pattern.compile(规则);


<11> 异常不要用来做流程控制、条件控制

说明:异常设计的初衷是解决程序运行中的各种意外情况,况且异常的处理效率比条件判断方式要低得多。


<12> catch时请分清稳定代码和非稳定代码。稳定代码指的是无论如何都不会出错的代码。对于非稳定代码的catch,尽可能在进行异常类型的区分后,再做对应的异常处理

说明:对大段代码进行try-catch,将使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题。这是一种不负责任的表现。

正例:在用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入的密码过于简单,在程序上会作出分门别类的判断,并提示用户。


<13> 避免出现重复的代码(Don't Repeat Yourself),即DRY原则。

说明:随意复制和粘贴代码,必然会导致代码的重复,当以后需要修改时,需要修改所有的副本,容易遗漏。必要时可抽取共性方法,或者抽象公共类,甚至将代码组件化。

正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:private boolean checkParam(DTO dto) {...}


<14> 对trace/debug/info级别的日志输出,必须使用条件输出形式或者占位符的方式

说明:logger.debug("Processing trade with id:" + id + " and symbol:" + symbol);,如果日志级别是warn,上述日志不会打印,但是会执行字符串拼接操作,如果symbol是对象,会执行toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有被打印。

正例:(条件)

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Processing trade with id:" + id + " and symbol:" + symbol);
        }

正例:(占位符)

LOGGER.debug("Processing trade with id:{} and symbol:{}", id, symbol);

<15>【MySQL】表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否)

说明:任何字段如果为非负数,则必须是unsigned。

正例:表达逻辑删除的字段名is_deleted,1表示删除,0表示未删除。


<16>【MySQL】禁用保留字,如desc、range、match、delayed等,请参考MySQL官方保留字

    说明:pk_即primary key;uk_即unique key;idx_即index的简称。


<17>【MySQL】主键索引名为pk_字段名,唯一索引名为uk_字段名,普通索引名则为idx_字段名


<18>【MySQL】小数类型为decimal,禁止使用float和double 

说明:在存储的时候,float和double存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过decimal的范围,建议将数据拆成整数和小数并分开存储。


<19>【MySQL】如果存储的字符串长度几乎相等,则应使用char定长字符串类型


<20>【MySQL】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000个字符。如果存储长度大于此值,则应定义字段类型为text,独立出来一张表,用主键来对应,避免影响其他字段的索引效率

猜你喜欢

转载自blog.csdn.net/weixin_30342639/article/details/86759186
今日推荐