Java业务开发常见谬误100例(代码篇-1)

此博客记录本人学习极客教程上《Java业务开发常见谬误100例》的集体总结,将依照课程体系三大部分,分成代码篇、设计篇和平安篇 三个章节。

01丨应用了并发工具类库,线程平安就居安思危了吗?

  1. 应用 ThreadLocal 来缓存数据,认为 ThreadLocal 在线程之间做了隔离不会有线程平安问题,没想到线程重用导致数据串了。请务必记得,在业务逻辑完结之前清理ThreadLocal 中的数据。
  2. 认为应用了 ConcurrentHashMap 就能够解决线程平安问题,没对复合逻辑加锁导致业务逻辑谬误。如果你心愿在一整段业务逻辑中,对容器的操作都放弃整体一致性的话,须要加锁解决。
  3. 没有充沛理解并发工具的个性,还是依照老形式应用新工具导致无奈施展其性能。比方,应用了ConcurrentHashMap,但没有充分利用其提供的基于 CAS 平安的办法,还是应用锁的形式来实现逻辑。
  4. CopyOnWriteArrayList虽是一个线程平安的ArrayList,它原理是 写时复制,因而实用于 读多写少的 业务场景下

02丨代码加锁:不要让“锁”事成为烦心事

  1. 应用 synchronized 加锁尽管简略,但咱们首先要弄清楚共享资源是类还是实例级别 的、会被哪些线程操作,synchronized 关联的锁对象或办法又是什么范畴的。
  2. 加锁尽可能要思考粒度和场景,锁爱护的代码意味着无奈进行多线程操作。对于 Web 类型的人造多线程我的项目,对办法进行大范畴加锁会显著降级并发能力,要思考尽可能 地只为必要的代码块加锁,升高锁的粒度;而对于要求超高性能的业务,还要细化思考锁的 读写场景,以及乐观优先还是乐观优先,尽可能针对明确场景精细化加锁计划,能够在适当 的场景下思考应用 ReentrantReadWriteLock、StampedLock 等高级的锁工具类。
  3. 业务逻辑中有多把锁时要思考死锁问题,通常的躲避计划是,防止有限期待和循环等 待。
  4. 此外,如果业务逻辑中锁的实现比较复杂的话,要认真看看加锁和开释是否配对,是否有遗 漏开释或反复开释的可能性;并且要思考锁主动超时开释了,而业务逻辑却还在进行的状况 下,如果别的线线程或过程拿到了雷同的锁,可能会导致反复执行。
  5. 如果你的业务代 码波及简单的锁操作,强烈建议 Mock 相干内部接口或数据库操作后对利用代码进行压 测,通过压测排除锁误用带来的性能问题和死锁问题。

03丨线程池:业务代码最罕用也最容易犯错的组件

  1. Executors 类提供的一些快捷申明线程池的办法尽管简略,但暗藏了线程池的参数细节。因而,应用线程池时,咱们肯定要依据场景和需要配置正当的线程数、工作队列、回绝策略、线程回收策略,并对线程进行明确的命名不便排查问题。
  2. 既然应用了线程池就须要确保线程池是在复用的,每次 new 一个线程池进去可能比不必线程池还蹩脚。如果你没有间接申明线程池而是应用其他同学提供的类库来取得一个线程池,请务必查看源码,以确认线程池的实例化形式和配置是合乎预期的。
  3. 复用线程池不代表应用程序始终应用同一个线程池,咱们应该依据工作的性质来选用
    不同的线程池。特地留神 IO 绑定的工作和 CPU 绑定的工作对于线程池属性的偏好,如果
    心愿缩小工作间的互相烦扰,思考按需应用隔离的线程池。

04丨连接池:别让连接池帮了倒忙

连接池构造示意图:(罕用连接池有:Redis 连接池、HTTP 连接池、数据库连接池)

  1. 连接池实现形式:客户端 SDK 实现连接池的形式,包含池和连贯拆散、外部带有连接池和非连接池三种。要
    正确应用连接池,就必须首先甄别连接池的实现形式。比方,Jedis 的 API 实现的是池和连贯拆散的形式,而 Apache HttpClient 是内置连接池的 API。
  2. 应用姿态:一是确保连接池是复用的,二是尽可能在程序退出之前显式敞开连接池开释资源。
  3. 连接池参数配置:最重要的是最大连接数,许多高并发利用往往因为最大连接数不
    够导致性能问题。但最大连接数不是设置得越大越好,而是够用就好。

05丨HTTP调用:你思考到超时、重试、并发了吗?

  1. 了解 连贯超时 和 读取超时的区别,学会如何设置 适合的 超时参数。此外,在应用诸如 Spring Cloud Feign 等框架时务必确认,连贯和读取超时参数的配 置是否正确失效。
  2. 对于重试,因为HTTP 协定认为 Get 申请是数据查问操作,是无状态的,又思考到网络出 现丢包是比拟常见的事件,有些 HTTP 客户端或代理服务器会主动重试 Get/Head 申请。 如果你的接口设计不反对幂等,须要敞开主动重试。但更好的解决方案是,应该是 听从 HTTP 协定的倡议来应用适合的 HTTP 办法。

06丨20%的业务代码的Spring申明式事务,可能都没解决正确

  1. 因为配置不正确,导致办法上的事务没失效,@Transactional 失效准则 :

    1. 除非非凡配置(比方应用 AspectJ 动态织入实现 AOP),否则只有定义在 public 办法上的 @Transactional 能力失效。

      起因:Spring 默认通过动静代理的形式实现 AOP,对指标办法进行加强,private 办法无奈代理到, Spring 天然也无奈动静加强事务处理逻辑。
    2. 必须通过代理过的类从内部调用指标办法能力失效
  2. 因为异样解决不正确,导致事务尽管失效但出现异常时没回滚,Spring 默认只会对标记 @Transactional 注解的办法呈现了 RuntimeException 和 Error 的时候回滚,如果咱们的办法捕捉了异样,那么须要通过手动编码处理事务回滚。如果心愿 Spring 针对其余异样也能够回滚,那么能够相应配置 @Transactional 注解的 rollbackFor 和noRollbackFor 属性来笼罩其默认设置。
  3. 如果办法波及屡次数据库操作,并心愿将它们作为独立的事务进行提交或回滚,那么咱们须要思考进一步细化配置事务传播方式,也就是 @Transactional 注解的Propagation 属性。

07丨数据库索引:索引并不是万能药

  1. InnoDB 是如何存储数据的?

    1. 尽管数据保留在磁盘中,但其解决是在内存中进行的。为了缩小磁盘随机读取次数,
      InnoDB 采纳而不是行的粒度来保留数据,即数据被分成若干页,以页为单位保留在磁盘
      中。InnoDB 的页大小,个别是 16KB。
      各个数据页组成一个双向链表,每个数据页中的记录依照主键程序组成单向链表;每一个数
      据页中有一个页目录,不便依照主键查问记录。数据页的构造如下:

    2. 页目录通过槽把记录分成不同的小组,每个小组有若干条记录。如图所示,记录中最后面的 小方块中的数字,代表的是以后分组的记录条数,最小和最大的槽指向 2 个非凡的伪记 录。有了槽之后,咱们依照主键搜寻页中记录时,就能够采纳二分法疾速搜寻,无需从最小 记录开始遍历整个页中的记录链表。
  2. 聚簇索引和二级索引

    1. InnoDB 应用 B+ 树,既能够保留理论数据,也能够减速数据搜寻,这就是聚簇索引。因为数据在物理上只会保留一份,所以蕴含理论数据的聚簇索引只能有一个
    2. 为了实现非主键字段的疾速搜寻,就引出了二级索引,也叫作非聚簇索引、辅助索引。二级 索引,也是利用的 B+ 树的数据结构
  3. 不是所有针对索引列的查问都能用上索引

    1. 第一,索引只能匹配列前缀
    2. 第二,条件波及函数操作无奈走索引
    3. 第三,联结索引只能匹配右边的列
  4. 解决几个误区:

    1. 思考到索引的保护代价、空间占用和查问时回表的代价,不能认为索引越多 越好。索引肯定是按需创立的,并且要尽可能确保足够轻量。
    2. 不能认为建了索引就肯定无效,对于后缀的匹配查问、查问中不蕴含联结索 引的第一列、查问条件波及函数计算等状况无奈应用索引。此外,即便 SQL 自身合乎索引 的应用条件,MySQL 也会通过评估各种查问形式的代价,来决定是否走索引,以及走哪个 索引。

08丨判等问题:程序里如何确定你就是你?

  1. 首先,咱们要留神 equals 和 == 的区别。业务代码中进行内容的比拟,针对根本类型只能应用 ==,针对 Integer、String 在内的援用类型,须要应用 equals。Integer 和 String的坑在于,应用 == 判等有时也能取得正确后果(JVM缓存,例如:Integer会缓存[-128,127])。
  2. 其次,对于自定义类型,如果类型须要参加判等,那么务必同时实现 equals 和 hashCode办法,并确保逻辑统一。如果心愿疾速实现 equals、hashCode 办法,咱们能够借助 IDE的代码生成性能,或应用 Lombok 来生成。如果类型也要参加比拟,那么 compareTo 办法的逻辑同样须要和 equals、hashCode 办法统一。
  3. 最初,Lombok 的 @EqualsAndHashCode 注解实现 equals 和 hashCode 的时候,默认应用类型所有非 static、非 transient 的字段,且不思考父类。如果心愿扭转这种默认行为,能够应用 @EqualsAndHashCode.Exclude 排除一些字段,并设置 callSuper = true来让子类的 equals 和 hashCode 调用父类的相应办法。

09丨数值计算:留神精度、舍入和溢出问题

  1. 务必不要应用Double作为金钱数值计算,因为浮点数计算会造成精度损失
  2. BigDecimal比拟value,请应用compareTo
  3. 第一,切记,要准确示意浮点数应该应用 BigDecimal。并且,应用 BigDecimal 的Double 入参的构造方法同样存在精度失落问题,应该应用 String 入参的构造方法或者BigDecimal.valueOf 办法来初始化
  4. 第二,对浮点数做准确计算,参加计算的各种数值应该始终应用 BigDecimal,所有的计算都要通过 BigDecimal 的办法进行,切勿只是让 BigDecimal 来走过场。任何一个环节呈现精度损失,最初的计算结果可能都会呈现误差
  5. 第三,对于浮点数的格式化,如果应用 String.format 的话,须要意识到它应用的是四舍五入,能够思考应用 DecimalFormat 来明确指定舍入形式。但思考到精度问题,我更倡议应用 BigDecimal 来示意浮点数,并应用其 setScale 办法指定舍入的位数和形式
  6. 第四,进行数值运算时要小心溢出问题,尽管溢出后不会出现异常,但失去的计算结果是齐全谬误的。咱们思考应用 Math.xxxExact 办法来进行运算,在溢出时能抛出异样,更倡议对于可能会呈现溢出的大数运算应用 BigInteger 类

10丨汇合类:坑满地的List列表操作

  1. Arrays.asList 和 List.subList的应用

    1. Arrays.asList 失去的是 Arrays 的外部类 ArrayList,List.subList 失去的是 ArrayList 的
      外部类 SubList,不能把这两个外部类转换为 ArrayList 应用。
    2. Arrays.asList 间接应用了原始数组,能够认为是共享“存储”,而且不反对增删元素;
      List.subList 间接援用了原始的 List,也能够认为是共享“存储”,而且对原始 List 间接
      进行结构性批改会导致 SubList 出现异常。
    3. 对 Arrays.asList 和 List.subList 容易疏忽的是,新的 List 持有了原始数据的援用,可能
      会导致原始数据也无奈 GC 的问题,最终导致 OOM。
  2. Arrays.asList 不肯定能够把所有数组转换为正确的 List。当传入根本类型数组的时候,List 的元素是数组自身,而不是数组中的元素
  3. 搜寻超大 ArrayList 的时候遇到性能问题。咱们思考利用 HashMap 哈希表随机查找的工夫复杂度为 O(1) 这个个性来优化性能,不过也要思考 HashMap 存储空间上的代价,要均衡工夫和空间
  4. 百分之九十的状况下,LinkedList在读写性能都没有ArrayList好