从错误中学习 2:Java 的三个阻塞队列
鉴于 Java 中有几种常用的阻塞队列,例如 ArrayBlockingQueue、LinkedBlockingQueue 和 SynchronousQueue,我认为基于我们发现的问题以及我们如何解决它,写一篇简短的文章对其他程序员来说会很有用。
最近,我和我的程序员团队发现应用程序请求机制存在一个小的故障。经过几次检查,我们发现故障的根本原因是由使用 BlockingQueue
引起的。鉴于 Java 中有几种常用的 BlockingQueues
,例如 ArrayBlockingQueue
、LinkedBlockingQueue
和 SynchronousQueue
,我认为基于我们发现的问题以及我们如何解决它,写一篇简短的文章对其他程序员来说会很有用。
故障与诊断
故障的症状很简单,但却很麻烦——应用程序处理请求的线程池已满,因此无法处理其他请求。转储线程后,我们看到线程堆栈阻塞在日志写入区域。线程堆栈阻塞在日志写入区域并非新问题,但以前我们认为这是由其他原因造成的。因此,根据过去的经验,我们假设问题不在日志写入区域。
在使用许多故障排除方法并因找不到根本原因而感到沮丧后,我们回去查看了源代码,试图找到导致问题的线索。我们发现代码中有一个日志锁。从这个日志锁中,我们可以从线程堆栈中看到 ArrayBlockingQueue.put
发生了阻塞。经过进一步检查,我们发现它是一个长度为 1024 的 BlockingQueue
。这意味着如果此队列中有 1024 个对象,后续的 Put 请求将被阻塞。编写代码的程序员已经考虑过,如果 BlockingQueue
满了,数据应该被处理。这是有问题的代码
if (blockingQueue.remainingCapacity() < 1) { //todo } blockingQueue.put Here, there are two main parts to the problem: 1. A complete judgment goes directly to 'put', rather than 'else'. 2. After the queue is full, the processing logic is still //todo...
上面的代码显示了这位程序员对 BlockingQueue
接口缺乏熟悉。要实现这个结果,您不需要先进行这种判断。更好的方法是使用 blockingQueue.offer
。如果它返回‘false',那么您可以实现相关的异常处理。
阻塞队列的类型
BlockingQueue
是生产者/消费者模式中常用的数据结构。最常用的类型是 ArrayBlockingQueue
、LinkedBlockingQueue
和 SynchronousQueue
。
ArrayBlockingQueue
和 LinkedBlockingQueue
之间的主要区别在于放入队列的对象。一个用于数组,另一个用于链表。其他区别也在代码注释中给出
在大多数并发应用程序中,链接队列通常比基于数组的队列具有更高的吞吐量,但性能更不可预测。
SynchronousQueue
是一个特殊的 BlockingQueue
。它在 offer 时使用。如果没有其他线程当前执行 take 或 poll,offer 将失败。在 take 时,如果没有其他线程同时执行 offer,它也将失败。这种特殊模式非常适合具有高响应要求和来自非固定线程池的线程的队列。
问题摘要
对于在线业务场景,在所有涉及并发和外部访问的阻塞区域都必须有超时机制。我不知道有多少次我看到缺乏超时机制是严重在线业务故障的根本原因。在线业务强调快速处理和完成请求。因此,快速失败是在线业务系统设计和代码编程中最重要的原则。根据这一原则,上述代码中最明显的错误是使用 put,而不是带超时机制的 offer。或者,可以说,对于不重要的场景,应该直接使用 offer,而‘false' 的结果将直接导致抛出或记录异常。
关于 BlockingQueue
场景,除了超时机制外,队列长度也必须有限制。否则,默认使用 Integer. MAX_VALUE
。在这种情况下,如果代码有 bug,内存将挂起。
在谈论 BlockingQueue
时,我们还应该提到 BlockingQueue
最常用的区域——线程池。Java 的 ThreadPoolExecutor
有 BlockingQueue
参数。如果这里使用了 ArrayBlockingQueue
或 LinkedBlockingQueue
并且线程池的 coreSize
和 poolSize
不同,那么在 coreSize
线程被占用后,线程池将首先向 BlockingQueue
发送 offer。如果成功,则过程结束。
然而,这种情况并不总是适合在线业务的需求。
在线业务在快节奏的环境中运行,需要快速处理,而不是将请求放入队列。事实上,对于在线业务而言,最好是请求根本不堆积在队列中。这种在线业务结构很容易导致雪崩,而直接拒绝系统处理能力之外的请求并抛出错误消息是一种相对简单但有效的方法。然而,这是一种限制机制。
请记住,在编写高度并发和分布式代码时,不仅仅是系统设计,还需要注意代码中的所有细微之处。这样才能避免出现上述问题。