You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中高频增删ArrayList引发NPE的优化解决方案问询

解决异步高频修改列表导致的遍历NPE问题及内存优化方案

首先,核心问题出在**ArrayList不是线程安全集合**,异步环境下的并发修改(移除首元素+添加尾元素)和遍历操作会破坏集合的内部一致性,最终导致NPE或并发修改异常。深拷贝之所以无效,是因为拷贝过程本身需要遍历原集合,此时仍可能被异步修改干扰。下面结合你的场景给出针对性解决方案:

一、最省心的线程安全替换:用ConcurrentLinkedQueue

你的操作是典型的FIFO滑动窗口,队列结构天然适配「移除头部+添加尾部」的逻辑,ConcurrentLinkedQueue是Java/Kotlin中线程安全的队列实现,它的迭代器是弱一致性的——遍历过程中不会因并发修改抛出异常,也不会返回null元素。

修改后的代码:

// 替换ArrayList为ConcurrentLinkedQueue
private val buffer = ConcurrentLinkedQueue<CustomObj>()

private fun observeFrequentData() {
    frequentData.observe(owner, Observer { data ->
        accelerationData ?: return@Observer
        GlobalScope.launch {
            val a = data[0].toDouble()
            val b = data[1].toDouble()
            val c = a + b
            val timestamp = System.currentTimeMillis()
            val customObj = CustomObj(c, timestamp)
            
            // 超过容量时移除头部,O(1)操作(比ArrayList的removeAt(0)高效太多)
            if (buffer.size >= 5000) {
                buffer.poll()
            }
            // 添加尾部元素,O(1)操作
            buffer.offer(customObj)
        }
    })
}

fun getBuffer() {
    // 迭代器线程安全,不会出现NPE
    val mappedData = buffer.map { it.smth }
}

二、坚持用列表?加同步锁控制并发

如果业务必须依赖列表结构,可以通过同步锁让修改和遍历操作互斥,彻底避免并发冲突。注意:ArrayList的removeAt(0)是O(n)操作(需要移动后续所有元素),当buffer大小为5000时,性能会比队列差一些。

修改后的代码:

private val buffer = ArrayList<CustomObj>()
// 用专用锁对象保证同步
private val bufferLock = Any()

private fun observeFrequentData() {
    frequentData.observe(owner, Observer { data ->
        accelerationData ?: return@Observer
        GlobalScope.launch {
            val a = data[0].toDouble()
            val b = data[1].toDouble()
            val c = a + b
            val timestamp = System.currentTimeMillis()
            val customObj = CustomObj(c, timestamp)
            
            // 修改操作加锁
            synchronized(bufferLock) {
                if (buffer.size >= 5000) {
                    buffer.removeAt(0)
                }
                buffer.add(customObj)
            }
        }
    })
}

fun getBuffer() {
    // 遍历操作也必须加锁,保证和修改互斥
    val mappedData = synchronized(bufferLock) {
        buffer.map { it.smth }
    }
}

三、内存最优方案:自定义环形缓冲区

如果追求极致的内存效率和性能,可以实现一个固定大小的环形缓冲区(循环数组)。它的内存占用固定(仅为设定的容量大小),添加和读取操作都是O(1),完全适配你的滑动窗口需求。

简化实现及使用:

class CircularBuffer<T>(private val capacity: Int) {
    private val array = arrayOfNulls<Any>(capacity) as Array<T>
    private var head = 0
    private var size = 0
    private val lock = Any()

    // 添加元素,满了自动覆盖最旧的头部元素
    fun add(element: T) {
        synchronized(lock) {
            val tail = (head + size) % capacity
            array[tail] = element
            if (size < capacity) {
                size++
            } else {
                head = (head + 1) % capacity
            }
        }
    }

    // 获取所有元素的线程安全方法
    fun getAll(): List<T> {
        synchronized(lock) {
            val result = mutableListOf<T>()
            for (i in 0 until size) {
                val index = (head + i) % capacity
                result.add(array[index])
            }
            return result
        }
    }
}

// 使用环形缓冲区
private val buffer = CircularBuffer<CustomObj>(5000)

private fun observeFrequentData() {
    frequentData.observe(owner, Observer { data ->
        accelerationData ?: return@Observer
        GlobalScope.launch {
            val a = data[0].toDouble()
            val b = data[1].toDouble()
            val c = a + b
            val timestamp = System.currentTimeMillis()
            val customObj = CustomObj(c, timestamp)
            // 无需手动移除,缓冲区自动维护大小
            buffer.add(customObj)
        }
    })
}

fun getBuffer() {
    val mappedData = buffer.getAll().map { it.smth }
}

为什么深拷贝解决不了问题?

深拷贝的本质是遍历原集合并复制元素,但遍历过程中原集合仍可能被异步修改:要么迭代器抛出ConcurrentModificationException,要么遍历到被移除的位置拿到null,根本无法从根源解决并发访问的冲突。如果要让深拷贝有效,必须在拷贝时加锁——但这样就和第二种方案逻辑一致,还多了一次拷贝的内存开销,完全没必要。

方案选择建议

  • 优先选ConcurrentLinkedQueue:代码改动最小,线程安全有保障,适合大部分场景
  • 追求极致性能/内存选环形缓冲区:固定内存占用,O(1)操作效率最高
  • 必须用列表才选同步锁方案:注意removeAt(0)的性能损耗

内容的提问来源于stack exchange,提问作者MaaAn13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:52:29