Java踩坑记及解决方案

2022-04-20  本文已影响0人  _Gaara_

ConcurrentLinkedQueue事故

伪代码:

    private static final long MAX_QUEUE_SIZE = 1_0000_0000;
    private ConcurrentLinkedDeque<T> linkedDeque = new ConcurrentLinkedDeque<T>();
    public void testQueue(T event){
        if (linkedDeque.size() < MAX_QUEUE_SIZE){
            linkedDeque.add(event);
        }
    }

乍一看解决的不错,考虑到了任务超出的抛弃,而且支持高并发。
但是有一个潜在威胁就是当队列的上限足够大时,频繁的使用size,会导致CPU占用瞬间飙升甚至100%。
原因是因为Concurrent系列的集合队列等size方法并不是插入时自增的标记位,而是通过遍历拿到最终结果。
那么时间复杂度就不是O(1)而是O(n)。

ArrayBlockingQueue的OOM

比如在异步日志的打印中,日志事件的载体就是ArrayBlockingQueue,一个有界队列。
如果设置的队列太大,就会产生内存溢出。所以需要限制些策略,比如设置临界值,队列容量达到临界值时,根据配置的抛弃策略,选择挑选符合条件的事件入队。比如异步日志中,达到临界时,选择丢弃小于INFO_INT级别的的任务。put()方法是阻塞的,所以选择不抛弃任务策略时,使用put,会阻塞起来等待队列空闲。抛弃策略则是使用offer()方法,如果满,则直接返回。

ConcurrentHashMap注意事项

比如在并发情况下,如果需要同时收集各自服务上的信息,如果使用ConcurrentHashMap,虽然保证了并发下的线程安全,但是却忽略了map.put()最原始的,Key相同则value覆盖的机制。所以当有此类事件的时候,选用map.putIfAbsent()方法。putIfAbsent会返回当前重复key的value,就可以将这部分内容重新读写。

SimpleDateFormat 线程不安全

多线程模式下,SimpleDateFormat是不安全的。因为内部实现的方式是将日期数据设置cal对象。当其他线程走到这里的时候刚好删掉了这个对象,则会造成异常。
解决办法:
1,每次都new一个新的SimpleDateFormat。但是有些浪费内存。
2,加锁,synchronized。但是竞争锁会消耗资源。
3,使用ThreadLocal。只实现一个SimpleDateFormat实例,节省资源。但是要记得使用完毕清理内存。否则会造成内存泄露。也就是ThreadLocal的问题。threadlocal.remove();

Timer的实现原理

定时任务Timer的底层是使用了一个TaskQueue。平衡二叉树堆实现的优先级队列。这里有一个缺陷就是一旦产生Interrupted Exception以外的异常,将会导致整个队列内其他的任务全部被清除。所以需要额外注意,队列任务内各种异常的捕获。日常开发中,定时器优先考虑ScheduledThreadPoolExecutor。

上一篇 下一篇

猜你喜欢

热点阅读