Java- 技术专题 - 挖掘陷阱系列(1-10)
1.开发过程中使用的 ThreadLocal 出现的问题
可能出现数据紊乱:使用线程池之后,没有强制清楚每一个线程内部的 ThreadLocal 对象的 ThreadLocalMap 的数据,因为线程池本身为复用线程的存在,一旦没有在线程的 init 方法中重置或者使用完清空,则会出现数据紊乱,获取到上一个线程执行的数据。
可能出现内存泄漏:因为考虑到 ThreadLocalMap 存在内部的 Entry 对象,为 weakReference 对象弱引用,非强引用对象(此处误解,不是内存泄漏的根本原因),主要是 ThreadLocalMap 对象中的对象引用的生命周期与 Thread 的线程所在生命周期持平,因为 key 为弱引用,故此会被清除 null,此后,只有在 ThreadLocalMap 获取 get 方法或者 set 的时候,value 才会被回收,故此只有在手动清除之后,才是最完美的解决方案。
2.开发过程中使用的 ConcurrentHashMap 一定所有操作都是线程安全的吗
ConcurrentHashMap 只能保证的原子读写操作(putIfAbsent、computeIfAbsent、putIfPresent、replace、compute)等操作属于原子且线程安全的。
如果要确保多个原子行操作整体的线程安全,需要自己手动加锁,(size、isEmpty、containsValue)等聚合方法,并发时候可能反映的就是 ConcurrentHashMap 的中间状态。因此在并发情况下,返回值只能作为参考使用。
3.开发过程中使用的 ConcurrentHashMap 但是性能没有的到提高
ConcurrentHashMap 使用了那样如 HashMap 的方式进行加锁的方式操作,使用 ConcurrentHashMap
建议可以考虑使用原子化操作的 CAS 服务的轻量级乐观锁进行控制处理操作机制。(computeIfPresent、computeIfAbsent、putIfAbsent,getOrDefault())
4.开发过程中使用了 CopyOnWriteArrayList 导致的性能问题
因为目前的 COW 服务框架,使用的为两个内部拷贝的数据集合进行切换读写快照,使得读操作和写操作进行相互隔离机制,同时也避免了大量的拷贝数据的开销。
但伴随的相同的问题,仍有 50%的数据的空间浪费,所以如果充分考虑的数据集合最好为一次性初始化 COW 容器或者很少更新或者操作 COW 中数据信息的场景我们才会使用 COW 容器技术。
5.开发过程中没有理清楚线程安全的问题所在点,导致锁无效
需要识别线程不安全的范围和原因以及场景问题,在对症下药,进行控制相关的锁的范围以及粒度。
单纯考虑 Lock 锁的概念基本属于实例或者方法范围。资源更多的与场景和类有关系,所以要多注意是否资源和锁的关系和范围
6.开发过程中,没有考虑锁的粒度,可能会导致性能的问题。
单纯考虑锁的范围,可能会因小失大,所以锁的粒度如果过大,会导致整体代码无法优化以及串行化处理无法实现多核 cpu 的运作机制,大大降低吞吐以及降低性能。
所以希望可以尽可能的降低锁的粒度,对必要的代码块或者资源进行管控保护即可
7.开发过程中,加锁没有考虑锁的场景,可能导致的性能的问题。
使用 StampedLock 的乐观锁工具对象,可以实现大大提高一些服务性能。
使用 ReadWriteLock 的读写锁可以将读和写场景划分的非常合理以及可管控。
使用 Lock 的时候一定需要多关注一下是否考虑真正的开启公平锁的方式,公平锁会导致队列排序以及性能下降。
8.开发过程中,多把锁时,要格外的小心死锁的问题(VisualVM)
在加锁的时候一定要注意锁的粒度和范围,尽可能的不要进行锁资源的交叉和重叠问题
在加锁的时候一定要主要锁的顺序和关系,最重要是释放锁的顺序也要保持绝对一致。
9.开发过程中,使用 Executors 工具生成像城池导致两种类型的 OOM
队列任务堆积过多的时候导致 OOM:newFixedThreadPool(队列为 LinkedBlockingQueue)和 newSingleThreadExecutor(队列为 LinkedBlockingQueue)
创建线程过多会导致出现 OOM:newCachedThreadPool(队列为 SynchonousQueue)和 newScheduledThreadPool()都是属于最大值线程为 Integer.max 值 会导致几乎无限的创建线程最后导致 OOM 发生。
故此建议考虑手动指定线程池的参数信息控制以及名字。
10.线程池的管理策略详解:如何实现一个更激进的线程池
重写自定义一个新的 BlockingQueue 的类对象机制,主要针对于相关的 BlockingQueue 的 offer 方法,不进入队列,直接返回 false,这样就会进入相关的创建线程。
重写自定义一个新的 RejectedExecutionHandler 的拒绝策略时候在把任务加入任务队列。
评论