常见错误

11空值处理

5种常见空值(Arthas)

参数值是 Integer 等包装类型判空,可以使用 Optional.ofNullable字符串比较, String 和字面量的比较,字面量放在前面,字符串和字符变量可以使用Objects.equals(s, t)ConcurrentHashMap,Key 和 Value 都不支持 null,使用HashMapA 对象包含了 B,在通过 A 对象的字段获得 B 之后,没有对字段判空就级联调用 B 的方法出现空指针异常;方法或远程服务返回的 List 不是空而是 null

POJO字段设置默认值,导致数据库原始值被覆盖,Entity属性设为不为空,先查询实体再动态更新,Hibernate的DynamicUpdate

有关 NULL 的三个坑(sum,count,null值条件)

客户端不传某个属性,或者传 null,这个属性在 DTO 中都是 null,可以借助 Optional 来解决

12异常处理

常见的错误异常的处理方式

不在业务代码层面考虑异常处理,仅在框架层面粗犷捕获和处理异常。捕获了异常后直接生吞只记录了异常消息,却丢失了异常的类型、栈等重要信息完全不记录原始异常抛出异常时不指定任何消息。

除了通过日志正确记录异常原始信息外,通常还有三种处理模式:

转换重试恢复

finally抛出的异常可能会覆盖try 中的异常,一个方法无法出现两个异常

finally 代码块自己负责异常捕获和处理:把 try 中的异常作为主异常抛出,使用 addSuppressed 方法把 finally 中的异常附加到主异常上实现了 AutoCloseable 接口的资源,使用try-with-resources 模式

别把异常定义为静态变量

线程池通过excute提交任务出现异常,异常的抛出老线程退出了,线程池只能重新创建一个线程

以 execute 方法提交到线程池的异步任务,最好在任务内部做好异常处理设置自定义的异常处理程序作为保底,比如在声明线程池时自定义线程池的未捕获异常处理程序

线程池通过submit提交任务出现异常,线程没退出,异常也没记录被生吞了

通过task.get()获取异常

3线程池

线程池的声明需要手动进行,小心OOM 的问题

newFixedThreadPool(LinkedBlockingQueue 是一个 Integer.MAX_VALUE 长度的队列,可以认为是无界的)newCachedThreadPool(最大线程数是 Integer.MAX_VALUE,可以认为是没有上限的,而其工作队列 SynchronousQueue 是一个没有存储空间的阻塞队列。)

实现激进的线程池

重写队列的 offer 方法,造成这个队列已满的假象自定义的拒绝策略处理程序,这个时候再把任务真正插入队列

线程池本身要复用

混用线程池导致的性能问题

执行比较慢、数量不大的 IO 任务,不需要太大的队列。吞吐量较大的计算型任务,线程数量不宜过多,需要较长的队列来做缓冲。

拒绝策略

CallerRunsPolicy调用者运行策略

不允许失败的、对性能要求不高、并发量较小的场景下使用当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理。 AbortPolicy(中止策略)

当触发拒绝策略时,直接抛出拒绝执行的异常,打断当前执行流程ThreadPoolExecutor中默认的策略就是AbortPolicy DiscardPolicy(丢弃策略) DiscardOldestPolicy(弃老策略)

如果线程池未关闭,就弹出队列头部的元素,然后尝试执行发布消息,和修改消息场景

基本流程

不会初始化 corePoolSize 个线程,有任务来了才创建工作线程;

调用 prestartAllCoreThreads 方法,来启动所有核心线程; 当核心线程满了之后不会立即扩容线程池,而是把任务堆积到工作队列中; 当工作队列满了后扩容线程池,一直到线程个数达到 maximumPoolSize 为止; 如果队列已满且达到了最大线程后还有任务进来,按照拒绝策略处理; 当线程数大于核心线程数时,线程等待 keepAliveTime 后还是没有任务需要处理的话,收缩线程到核心线程数。

传入 true 给 allowCoreThreadTimeOut 方法,来让线程池在空闲的时候同样回收核心线程。

8判等问题

比较值的内容,除了基本类型只能使用 == 外,其他类型都需要使用 equals

不能使用==判等的几种情况

Interger包装类[-128,127]外的值)new 出来的两个对象,比较的是引用intern 方法也会走常量池机制,大量使用会影响性能

实现equals方法的坑

equals和hascode没有配对实现的坑,Point(x,y)例子

equals和compareTo实现逻辑不一致的坑Student的例子

Lombok的EqualsAndHashCode的坑,比如继承父类的时候

getClass (只能是同一个类)和 instanceof(允许是子类)区别

19、20Spring框架

注意Bean默认单例的问题(SayService例子),对于有状态的类型,单例可能产生内存泄露问题。

单例的Bean注入多例的Bean不仅设置Scope,还要设置proxyMode就是走代理模式或者从ApplicationContext中获取

自定义Aspect因为顺序问题导致Spring事务失效的坑(Metrics例子)我的理解:如果自定义切面先吃掉异常,那么导致事务的AOP的没有捕获到异常就没有回滚,所以事务失效了

Fegin AOP切不到的诡异案例(FeginClient的Url案例)ApacheHttpClient是final的,不能使用CGLIB代理

配置文件中配置不生效的问题

环境 Environment 接口为入口

PropertySourcesPropertyResolver

6数据库事务

两种错误写法导致Spring事务未生效的坑

1,除非特殊配置(比如使用 AspectJ 静态织入实现 AOP),否则只有定义在 public 方法上的 @Transactional 才能生效2,必须通过代理过的类从外部调用目标方法才能生效。

事务即便生效也不一定能回滚

只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚。默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring 才会回滚事务。

不出异常,事务居然也不会提交?

用户和子用户例子propagation = Propagation.REQUIRES_NEW开启新事务,主方法出现异常不影响子方法propagation = Propagation.NESTED主方法出现异常,子方法的嵌套事务也会回滚。

31-32 Java8

Lambda 表达式和 Stream 操作

函数式 API 的增强

15序列化

序列化和反序列化需要确保算法一致redistemplate

RedisTemplate 默认使用 JdkSerializationRedisSerializer即使我们定义 RedisTemplate 的 Value 泛型为实际类型,查询出的 Value 也只能是 LinkedHashMap 类型,如果希望直接获取真实的数据类型,你可以启用 Jackson ObjectMapper 的 activateDefaultTyping 方法,把类型信息一起序列化保存在 Value 中。或者RedisSerializer.json()注意 Jackson JSON 反序列化对额外字段的处理反序列化时要小心类的构造方法枚举作为API接口参数或返回值的两个大坑

14文件IO

文件读写需要确保字符编码一致

使用Files类静态方法进行文件操作注意释放文件句柄

注意读写文件要考虑设置缓冲

5HTTP调用

配置连接超时和读取超时参数的学问

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: