美文网首页
java常见的坑

java常见的坑

作者: 知止9528 | 来源:发表于2021-01-10 09:11 被阅读0次

数值计算BigDecimal
BigDecimal核心是精度,精度如果不匹配,结果大概率不会符合预期

  1. 初始化设置精度需要匹配

  2. 除法结果需要精度

说明:不设置时,可能会出现除不尽的错误

  1. 数值比较需要精度匹配

说明:等于比较的时候,要设置精度


日期计算SimpleDateFormat

  1. 它可以解析大于/等于它定义的时间精度,但不能解析小于它定于的时间精度

2.它是线程不安全的,在多线程环境下操作,会抛异常

解决思路,定义为局部变量,或者使用ThreadLoacl,或者使用synchronizez


Object的equals和hashcode方法最好同步实现

类实现了compareTo方法,就需要实现equals方法


lombok的坑
lombok默认的equalAndhashCode是不包含父字段的
如果要包含需要手动加上@EqualsAndHashCode(callSuper=true)注解


序列化的一些坑

  1. 尽量避免单字母驼峰命名
    比如 iPhone,在jsckson序列化时会变成iphone,字段不对应,容易造成值丢失.
    单字母驼峰是不符合java命名规范的

  2. 子类实现java的序列化接口,父类没有实现,则父类必须有无参构造函数,否则子类序列化会报错

  3. 类中存在引用对象,则相关的引用对象都需要实现序列化接口,否则会报错

  4. 同一个对象多次序列化(之间有属性更新),依然还是以第一次为主

这是Java序列化的特性,如果已经序列化过,会有一个版本号记录,然后存储到磁盘,后续再次序列化如果版本号不变,拿到的还是之前的序列化结果


反射

  1. 方法的参数是基本类型,反射获取Method参数类型必须一致

如在对象里定义的是int类型,则反射时也需要是int类型,使用Integer类型是执行不成功的

  1. 区分getDeclaredMethod(只能获取自己的)和getMethod(可以获取到父类的方法)

字符串拼接

不要依赖与Jvm帮我们做的拼接优化,因为有时候可能会失效.不要直接 a+b+c这种,而是使用StringBuilder.apend


深拷贝与浅拷贝

java的clone接口是浅拷贝,可以通过序列化与反序列化实现深拷贝


Synchronized需要区分是类锁还是实例锁

  1. 加锁前要清楚锁和被保护的对象是不是一个层面的

静态字段属于类,类级别的锁才能保护;而非静态字段属于类实例,实例级别的锁就可以保护。

  1. 加锁要考虑锁的粒度和场景问题

即使我们确实有一些共享资源需要保护,也要尽可能降低锁的粒度,仅对必要的代码块甚至是需要保护的资源本身加锁。

  1. 如果精细化考虑了锁应用范围后,性能还无法满足需求的话,我们就要考虑另一个维度的粒度问题了,即:区分读写场景以及资源的访问冲突,考虑使用悲观方式的锁还是乐观方式的锁。

对于读写比例差异明显的场景,考虑使用 ReentrantReadWriteLock 细化区分读写锁,来提高性能。
如果你的 JDK 版本高于 1.8、共享资源的冲突概率也没那么大的话,考虑使用 StampedLock 的乐观读的特性,进一步提高性能。
JDK 里 ReentrantLock 和 ReentrantReadWriteLock 都提供了公平锁的版本,在没有明确需求的情况下不要轻易开启公平锁特性,在任务很轻的情况下开启公平锁可能会让性能下降上百倍。


使用队列时需要区分好各个方法的使用

image.png

并发容器copyOnWrite需要注意使用场景

它适用于读多写少的情况,否则写多的时候,会不断的复制,最终可能导致内存耗尽


ThreadLocal使用完要记得remove

否则可能会造成内存泄漏或者使用的依然是上一次的副本


Spring的坑

spring通过构造器注入是不能解决循环依赖的,单可以提前发现循环依赖
说明:spring解决循环依赖的原理是通过在 实例化->属性赋值->初始化
在属性赋值之前提早暴露引用对象放置在三级缓存,来解决的.
通过构造器初始化,就直接初始化了

Transactional不能回滚的坑

Spring的Transactional是针对代理对象,如果直接在当前类,使用的是this本身未被代理的对象。所以最好需要Transactional的方法单独放一个类对象,并交给spring进行管理.需要使用的地方通过autowire进行注入


线程池

  1. 不要使用java的线程池工具类

使用自定义的,而不是工具类提高的,工具类的可能会导致创建无限的线程或者无线队列,会导致栈空间,或者内存空间耗尽

  1. 对于执行比较慢、数量不大的 IO 任务,或许要考虑更多的线程数,而不需要太大的队列。而对于吞吐量较大的计算型任务,线程数量不宜过多,可以是 CPU 核数或核数 *2(理由是,线程一定调度到某个 CPU 进行执行,如果任务本身是 CPU 绑定的任务,那么过多的线程只会增加线程切换的开销,并不能提升吞吐量),但可能需要较长的队列来做缓冲。

  2. 线程池的关闭

推荐使用shutdown和awaitTERMINATED
直接stop可能会有bug,可能还没执行完就直接stop了

线程池的状态转换图.png
  1. Java 8 的 parallel stream 功能,可以让我们很方便地并行处理集合中的元素,其背后是共享同一个 ForkJoinPool,默认并行度是 CPU 核数 -1。对于 CPU 绑定的任务来说,使用这样的配置比较合适,但如果集合操作涉及同步 IO 操作的话(比如数据库操作、外部服务调用等),建议自定义一个 ForkJoinPool(或普通线程池)

  2. 务必确认清楚线程池本身是不是复用的

  3. 如果提交到线程池中的任务比较重要,可能需要持久化执行状态到数据库

如果没有持久化,任务又比较重要,线上机器突然宕机的时候,队列里的任务也会丢失
如果持久化了,则机器重启的时候,通过定时任务扫面数据库,再执行没执行成功的任务即可



SpringMvc中的坑
HttpServletRequest和HttpServletResponse

1.Request的getInputStream()和getReader()都只能使用一次
2.Request的getInputStream(),getReader(),getParameter()是互斥的,也就是使用了其中一个,再使用另外两个,是获取不到数据的
3.Response也是一样
解决方案:
使用HttpServletRequest
Wrapper+Filter解决输入流不能重复读取的问题


ConcurrentHashMap的坑

  1. 使用了 ConcurrentHashMap,不代表对它的多个操作之间的状态是一致的,是没有其他线程在操作它的,如果需要确保需要手动加锁。诸如 size、isEmpty 和 containsValue 等聚合方法,在并发情况下可能会反映 ConcurrentHashMap 的中间状态。因此在并发情况下,这些方法的返回值只能用作参考,而不能用于流程控制。显然,利用 size 方法计算差异值,是一个流程控制。诸如 putAll 这样的聚合方法也不能确保原子性,在 putAll 的过程中去获取数据可能会获取到部分数据。

  2. ConcurrentHashMap 提供了一些原子性的简单复合逻辑方法,用好这些方法就可以发挥其威力。

如使用 ConcurrentHashMap 的原子性方法 computeIfAbsent 来做复合逻辑操作,判断 Key 是否存在 Value,如果不存在则把 Lambda 表达式运行后的结果放入 Map 作为 Value,也就是新创建一个 LongAdder 对象,最后返回 Value。
由于 computeIfAbsent 方法返回的 Value 是 LongAdder,是一个线程安全的累加器,因此可以直接调用其 increment 方法进行累加。


相关文章

网友评论

      本文标题:java常见的坑

      本文链接:https://www.haomeiwen.com/subject/bubcaktx.html