公众号|松华说|Java业务开发易错点
  • 分享在京东工作的技术感悟,还有JAVA技术和业内最佳实践,大部分都是务实的、能看懂的、可复现的

扫一扫
关注公众号

Java业务开发易错点

博客首页文章列表 松花皮蛋me 2020-03-25 23:46

一、并发工具类库

1、没有显式使用多线程技术并不代表不存在线程安全的隐患,比如:使用Tomcat容器。

2、使用线程间隔离而在方法或类共享的工具后需要显式进行清理,比如:使用ThreadLocal工具。

3、单个操作是线程安全的,但是不代表多个组合是安全的。比如:先后使用ConcurrentHashMap的Get和Put方法。

4、要充分理解工具库的特性,让其在合适的场景下发挥真正的作用。比如:使用ConcurrentHashMap的putIfAbsent代替Get\Put组合操作,再比如:数据变化频繁的情况不要使用CopyOnWrite技术。


二、并发锁

1、没有分析清楚线程、业务逻辑、锁三者关系随意加锁,导致加锁无效。比如:比较操作在字节码层面是三步,代码看似一行但也不是原子性的。

2、没有搞清楚锁和需要保护的对象是不是一个层面的,导致加锁失败。比如:静态字段属于类,类级别的类才有用,非静态字段属于类实例,实例级别的锁才有用。

3、要注意锁对象的粒度。必要时考虑读写锁和乐观锁,甚至能不用锁就不要用锁。

4、多把锁要小心死锁问题。比如:加锁时没有注意顺序,导致循环等待资源释放。

5、加锁时要考虑锁超时的场景,避免到期自动释放,其他线程从而获取相同的锁然后执行重复的逻辑。比如:单独启动一个线程来避免锁的超时、保证业务支持幂等操作。

6、要避免误用锁导致性能吞吐量下降。比如:通过压测排除锁误用引起的性能问题。

三、线程池

1、不要使用底层为无界队列的线程池。比如:使用Executors 框架快速创建newFixedThreadPool ,存在OOM隐患。

2、线程池要可控,要确保能满足预期。比如:使用有意义的线程名、设定好拒绝策略、设定好回收策略等。

3、区分业务场景合理线程池。比如:IO密集型和CPU计算型业务的线程池中线程属性应该是不同的。

4、不要盲目复用他人的线程池业务代码,容易导致相互干扰。


四、连接池

1、要正确鉴别连接池的实现方式,到底是池和连接分离,还是内置连接池。

2、确保连接池是复用的,确保程序退出前显式关闭连接释放资源。

3、要做好最大连接数的监控报警,并结合业务容量规划及时调整。

4、要注意线程安全问题,避免导致数据错乱。比如:在多线程场景下使用JRedis操作redis对象是线程不安全的。


五、HTTP请求中的超时、重试

1、连接和读取数据超时时间不宜设置过长,正常1~3秒左右即可。

2、要利用好框架内置的重试逻辑,同时也要注意隐含的重试配置。比如:接口不支持幂等性POST请求,但是Spring Cloud Feign框架在请求超时后自动地进行了重试。


五、事务

1、不使用特殊配置或者动态织入时,在私有方法中使用事务注解不会生效,因为JDK动态代理默认通过继承增强,私有方法对其不可见。

2、使用this自调用事务方法是不会生效的。正确做法是注入self,再通过它调用。

3、默认情况下,出现非受检异常或者Error错误时,才会触发Spring事务回滚。

4、我们可以设置事务传播策略,保证子流程出现异常只有它自身回滚,而不会影响主流程。

5、不要在入口处依次调用不同的事务方法。


六、数值计算

1、对于数据很敏感的场景,比如金融类,需要使用浮点数来表示,那么就尽量使用BigDecimal。另外还要注意BigDecimal的构造方式,使用浮点数作为构造参数会出现精度缺失。

2、使用大数类计算工具时,应该自始至终地使用它,而不是声明时使用但是计算、比较时却使用传统方法。

3、浮点数格式化时需要避免四舍五入,数值累加时需要避免溢出。


七、集合类操作

1、需要注意构造或者切割时得到的可能只是原始视图,共享存储了原始对象,而不是新的对象,如果再基于视图进行增删改查很容易出现异常,甚至是内存溢出。比如Arrays.asList方法,再比如List的subList方法。

2、根据场景选择合适的数据结构,但是需要注意时间复杂度和空间复杂度。比如能使用HashMap进行搜索就不要使用ArrayList进行搜索,再比如链表LinkedList的插入效率就不如List的。


八、空值处理


空值的正确处理以及避免空指针异常,绝不是判空这么简单,还要根据业务属性从前到后仔细考虑。比如我们要避免把数据库字段错误地重置为空值,不然就会出现逻辑处理的不一致性。再比如数据库字段值为NULL时,使用sum、count函数计算时存在坑点。


九、日记记录

1、当对不同模块包采用不同配置时,需要注意覆盖问题,避免重复打印日记。

2、异步记录日记时,我们要避免产生内存溢出和日记丢失。所以要注意异步队列是有界还是无界的,假如是前者,队列空间用完后,服务会阻塞等待还是直接抛弃记录。

十、文件操作

1、读写编码不一致会出现乱码。

2、不恰当地一次性批量读取文件里的内容,容易产生OOM。所以我们应当使用流式处理方式,利用一块内存区域作为直接操作的过渡。

3、对于文件复制操作应该尽可能使用零拷贝技术,数据从磁盘经过总线直接发送到目标文件,无需经过内存和 CPU 进行数据中转。

十一、数据库中的时间类型


全球化项目处理日期和时间需要格外小心,比如要严格区分MySql中的时间储存类型datetime和timestamp,对于timestamp来说,它会自动检索当前时区并进行转换,而对于datetime来说,存储和读取出来的数据是完全一样的。


十二、内存溢出

1、一份数据在内存中是并不是等量存储的,它有可能会经过对象实体映射和包装。常见于资源下载的场景。

2、增大依赖资源的最大限制配置要慎重,比如增大Tomcat的最大请求头限制。


十三、反射、注解、泛型

1、反射进行方法调用要注意过滤桥接方法,这个桥接方法是泛型类型擦除后生成的。

2、子类以及子类的方法,无法自动继承父类和父类方法上的注解。


十四、代码重复


代码重复并不可怕,可怕的是迭代时漏改,或者将看似重复的代码错误地修改了。搞定代码重复的秘诀是稳定和变化分离,比如多参数多约束时,把变化的部分也就是规则的参数放入注解,规则的解析和校验通过反射统一处理。