JDK8 LinkedBlockingQueue遇到stream会出现死循环的情况
测试用例
public class TestQueue {
public static void main(String[] args) throws Exception {
LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<>(1000);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
queue.offer(new Object());
queue.remove();
}
}).start();
}
while (true) {
System.out.println("begin scan, i still alive");
queue.stream()
.filter(o -> o == null)
.findFirst()
.isPresent();
Thread.sleep(100);
System.out.println("finish scan, i still alive");
}
}
}
程序堵塞
原因
问题在于LinkedBlockingQueue的tryAdvance方法
stream遍历会调用tryAdvance方法
tryAdvance只有在下面的情况会出现死循环
- current != null 为true
- current.item != null 为false
众所周知LinkedBlockingQueue是链表结构
只有在LinkedBlockingQueue的一个节点自己指向自己的时候才会出现这种死循环的情况。
要把节点从链表上拿掉只可能在移除的时候
再看我们程序还使用了LinkedBlockingQueue的remove方法
里面调用了LinkedBlockingQueue的poll方法
poll方法里进行了加锁操作说明是线程安全的,重点看dequeue方法
自己指向自己来移除节点
dequeue 方法的这个地方和 tryAdvance 方法里面的 while 循环一起出现就会出现死循环的情况
如果调用有参的remove呢?
发现处理方式跟无参remove不一样
注释翻译 p.next没有更改,以允许遍历p的迭代器保持其弱一致性保证。
所以带参的 remove 方法是考虑到了迭代器的情况,但是无参的 remove 方法没考虑。
结论
LinkedBlockingQueue 虽然内部有读写锁的存在,一般情况下是线程安全的,但是,在 JDK8 的场景下,当它遇到 stream 操作的时候,又有其他线程在调用无参的 remove 方法,会有一定几率出现死循环的情况。
解决
JDK官方在1.9对此bug进行了修复