Java调用Native回调引发LinkedList并发异常问题排查
你的代码出现NullPointerException和ConcurrentModificationException,核心原因是非线程安全的LinkedList在并发场景下被修改与遍历,具体拆解如下:
可能的触发原因
回调与遍历的线程并发
虽然你提到Native代码未自行创建线程,但javacpp在处理JNI回调时,可能存在回调在其他线程触发的情况——比如Native依赖了系统底层的异步机制(如IO事件线程、信号处理线程),或是javacpp内部复用了线程池,导致handler.call()的add操作和scan()中的forEach遍历在不同线程同时执行。LinkedList本身没有任何线程安全保护,并发的修改与遍历会直接破坏其内部节点指针结构(比如迭代器的next指针变为null),或是触发快速失败机制抛出ConcurrentModificationException。回调执行时机超出预期
即使callToNativeLib()是同步调用,也可能存在Native在返回后仍触发回调的情况——比如Native内部使用了本地异步任务队列,在callToNativeLib()返回后才执行回调逻辑,此时forEach已经开始遍历,导致并发修改。
解决方案
针对这些问题,可以采用以下几种修复方式:
替换为线程安全集合
把LinkedList<Long>换成CopyOnWriteArrayList<Long>,它会在每次修改时复制底层数组,遍历操作基于快照进行,不会因并发修改抛出异常:private final CopyOnWriteArrayList<Long> matchedIds = new CopyOnWriteArrayList<>();或者使用
Collections.synchronizedList包装LinkedList,但遍历需要手动加锁:private final List<Long> matchedIds = Collections.synchronizedList(new LinkedList<>()); // 遍历的时候加锁 synchronized(matchedIds) { matchedIds.forEach(System.out::println); }确保回调完全完成后再遍历
如果回调确实应该在callToNativeLib()返回前全部执行,可添加同步机制确认所有回调完成,比如用CountDownLatch:class Scanner { private final LinkedList<Long> matchedIds = new LinkedList<>(); private CountDownLatch latch; private final Handler handler = new Handler() { public void call(int id) { matchedIds.add((long)id); latch.countDown(); } }; public void scan() throws InterruptedException { // 假设提前知道回调触发次数,或者从Native获取次数 latch = new CountDownLatch(预计回调次数); callToNativeLib(handler); latch.await(); // 等待所有回调完成 matchedIds.forEach(System.out::println); } }遍历前复制集合快照
在遍历前复制一份集合的快照,避免后续修改影响遍历过程:new ArrayList<>(matchedIds).forEach(System.out::println);
内容的提问来源于stack exchange,提问作者milan

