扫一扫
关注公众号
一、并发工具类库
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、子类以及子类的方法,无法自动继承父类和父类方法上的注解。
十四、代码重复
代码重复并不可怕,可怕的是迭代时漏改,或者将看似重复的代码错误地修改了。搞定代码重复的秘诀是稳定和变化分离,比如多参数多约束时,把变化的部分也就是规则的参数放入注解,规则的解析和校验通过反射统一处理。