数值计算BigDecimal
BigDecimal核心是精度,精度如果不匹配,结果大概率不会符合预期
-
初始化设置精度需要匹配
-
除法结果需要精度
说明:不设置时,可能会出现除不尽的错误
- 数值比较需要精度匹配
说明:等于比较的时候,要设置精度
日期计算SimpleDateFormat
- 它可以解析大于/等于它定义的时间精度,但不能解析小于它定于的时间精度
2.它是线程不安全的,在多线程环境下操作,会抛异常
解决思路,定义为局部变量,或者使用ThreadLoacl,或者使用synchronizez
Object的equals和hashcode方法最好同步实现
类实现了compareTo方法,就需要实现equals方法
lombok的坑
lombok默认的equalAndhashCode是不包含父字段的
如果要包含需要手动加上@EqualsAndHashCode(callSuper=true)注解
序列化的一些坑
-
尽量避免单字母驼峰命名
比如 iPhone,在jsckson序列化时会变成iphone,字段不对应,容易造成值丢失.
单字母驼峰是不符合java命名规范的 -
子类实现java的序列化接口,父类没有实现,则父类必须有无参构造函数,否则子类序列化会报错
-
类中存在引用对象,则相关的引用对象都需要实现序列化接口,否则会报错
-
同一个对象多次序列化(之间有属性更新),依然还是以第一次为主
这是Java序列化的特性,如果已经序列化过,会有一个版本号记录,然后存储到磁盘,后续再次序列化如果版本号不变,拿到的还是之前的序列化结果
反射
- 方法的参数是基本类型,反射获取Method参数类型必须一致
如在对象里定义的是int类型,则反射时也需要是int类型,使用Integer类型是执行不成功的
- 区分getDeclaredMethod(只能获取自己的)和getMethod(可以获取到父类的方法)
字符串拼接
不要依赖与Jvm帮我们做的拼接优化,因为有时候可能会失效.不要直接 a+b+c这种,而是使用StringBuilder.apend
深拷贝与浅拷贝
java的clone接口是浅拷贝,可以通过序列化与反序列化实现深拷贝
Synchronized需要区分是类锁还是实例锁
- 加锁前要清楚锁和被保护的对象是不是一个层面的
静态字段属于类,类级别的锁才能保护;而非静态字段属于类实例,实例级别的锁就可以保护。
- 加锁要考虑锁的粒度和场景问题
即使我们确实有一些共享资源需要保护,也要尽可能降低锁的粒度,仅对必要的代码块甚至是需要保护的资源本身加锁。
- 如果精细化考虑了锁应用范围后,性能还无法满足需求的话,我们就要考虑另一个维度的粒度问题了,即:区分读写场景以及资源的访问冲突,考虑使用悲观方式的锁还是乐观方式的锁。
对于读写比例差异明显的场景,考虑使用 ReentrantReadWriteLock 细化区分读写锁,来提高性能。
如果你的 JDK 版本高于 1.8、共享资源的冲突概率也没那么大的话,考虑使用 StampedLock 的乐观读的特性,进一步提高性能。
JDK 里 ReentrantLock 和 ReentrantReadWriteLock 都提供了公平锁的版本,在没有明确需求的情况下不要轻易开启公平锁特性,在任务很轻的情况下开启公平锁可能会让性能下降上百倍。
使用队列时需要区分好各个方法的使用
并发容器copyOnWrite需要注意使用场景
它适用于读多写少的情况,否则写多的时候,会不断的复制,最终可能导致内存耗尽
ThreadLocal使用完要记得remove
否则可能会造成内存泄漏或者使用的依然是上一次的副本
Spring的坑
spring通过构造器注入是不能解决循环依赖的,单可以提前发现循环依赖
说明:spring解决循环依赖的原理是通过在 实例化->属性赋值->初始化
在属性赋值之前提早暴露引用对象放置在三级缓存,来解决的.
通过构造器初始化,就直接初始化了
Transactional不能回滚的坑
Spring的Transactional是针对代理对象,如果直接在当前类,使用的是this本身未被代理的对象。所以最好需要Transactional的方法单独放一个类对象,并交给spring进行管理.需要使用的地方通过autowire进行注入
线程池
- 不要使用java的线程池工具类
使用自定义的,而不是工具类提高的,工具类的可能会导致创建无限的线程或者无线队列,会导致栈空间,或者内存空间耗尽
-
对于执行比较慢、数量不大的 IO 任务,或许要考虑更多的线程数,而不需要太大的队列。而对于吞吐量较大的计算型任务,线程数量不宜过多,可以是 CPU 核数或核数 *2(理由是,线程一定调度到某个 CPU 进行执行,如果任务本身是 CPU 绑定的任务,那么过多的线程只会增加线程切换的开销,并不能提升吞吐量),但可能需要较长的队列来做缓冲。
-
线程池的关闭
线程池的状态转换图.png推荐使用shutdown和awaitTERMINATED
直接stop可能会有bug,可能还没执行完就直接stop了
-
Java 8 的 parallel stream 功能,可以让我们很方便地并行处理集合中的元素,其背后是共享同一个 ForkJoinPool,默认并行度是 CPU 核数 -1。对于 CPU 绑定的任务来说,使用这样的配置比较合适,但如果集合操作涉及同步 IO 操作的话(比如数据库操作、外部服务调用等),建议自定义一个 ForkJoinPool(或普通线程池)
-
务必确认清楚线程池本身是不是复用的
-
如果提交到线程池中的任务比较重要,可能需要持久化执行状态到数据库
如果没有持久化,任务又比较重要,线上机器突然宕机的时候,队列里的任务也会丢失
如果持久化了,则机器重启的时候,通过定时任务扫面数据库,再执行没执行成功的任务即可
SpringMvc中的坑
HttpServletRequest和HttpServletResponse
1.Request的getInputStream()和getReader()都只能使用一次
2.Request的getInputStream(),getReader(),getParameter()是互斥的,也就是使用了其中一个,再使用另外两个,是获取不到数据的
3.Response也是一样
解决方案:
使用HttpServletRequest
Wrapper+Filter解决输入流不能重复读取的问题
ConcurrentHashMap的坑
-
使用了 ConcurrentHashMap,不代表对它的多个操作之间的状态是一致的,是没有其他线程在操作它的,如果需要确保需要手动加锁。诸如 size、isEmpty 和 containsValue 等聚合方法,在并发情况下可能会反映 ConcurrentHashMap 的中间状态。因此在并发情况下,这些方法的返回值只能用作参考,而不能用于流程控制。显然,利用 size 方法计算差异值,是一个流程控制。诸如 putAll 这样的聚合方法也不能确保原子性,在 putAll 的过程中去获取数据可能会获取到部分数据。
-
ConcurrentHashMap 提供了一些原子性的简单复合逻辑方法,用好这些方法就可以发挥其威力。
如使用 ConcurrentHashMap 的原子性方法 computeIfAbsent 来做复合逻辑操作,判断 Key 是否存在 Value,如果不存在则把 Lambda 表达式运行后的结果放入 Map 作为 Value,也就是新创建一个 LongAdder 对象,最后返回 Value。
由于 computeIfAbsent 方法返回的 Value 是 LongAdder,是一个线程安全的累加器,因此可以直接调用其 increment 方法进行累加。
网友评论