为什么ThreadPoolExecutor将BlockingQueue作为其参数?

Kan*_*mar 19 java multithreading

我尝试过创建和执行ThreadPoolExecutor

int poolSize = 2;
int maxPoolSize = 3;
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(2);
Run Code Online (Sandbox Code Playgroud)

如果我连续尝试7日,8日......任务

  threadPool.execute(task);  
Run Code Online (Sandbox Code Playgroud)

在队列达到最大大小后,
它开始抛出"RejectedExecutionException".意味着我失去了添加这些任务.

在这里,如果BlockingQueue缺少任务,那么它的作用是什么?意味着它为什么不等待?

从BlockingQueue的定义

一个队列,它还支持在检索元素时等待队列变为非空的操作,并在存储元素时等待队列中的空间可用.


为什么我们不能使用linkedlist(正常队列实现而不是阻塞队列)?

Kir*_*ril 16

出现此问题的原因是您的任务队列太小,这由execute方法的文档指示:

将来某个时候执行给定的任务.任务可以在新线程或现有池化线程中执行.如果无法提交执行任务,或者因为此执行程序已关闭或已达到其容量,则该任务由当前的RejectedExecutionHandler处理.

所以第一个问题是你将队列大小设置为一个非常小的数字:

int poolSize = 2;
int maxPoolSize = 3;
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(2);
Run Code Online (Sandbox Code Playgroud)

然后你说"如果[我]尝试第7,第8 ......任务"那么你会得到一个,RejectedExecutionException因为你超过了队列的容量.有两种方法可以解决您的问题(我建议两者兼顾):

  1. 增加队列的大小.
  2. 捕获异常并重新尝试添加任务.

你应该有类似的东西:

public void ExecuteTask(MyRunnableTask task) {
    bool taskAdded = false;
    while(!taskAdded) {
        try {
            executor.execute(task);
            taskAdded = true;
        } catch (RejectedExecutionException ex) {
            taskAdded = false;
        }
    }   
}
Run Code Online (Sandbox Code Playgroud)

现在,解决你的其他问题......

在这里,如果BlockingQueue缺少任务,那么它的作用是什么?

它的作用BlockingQueue是完成生产者/消费者模式,如果它足够大,那么你不应该看到你遇到的问题.如上所述,您需要增加队列大小并捕获异常,然后重试执行任务.

为什么我们不能使用链表?

链表既不是线程安全的,也不是阻塞的.生产者/消费者模式倾向于使用阻塞队列最佳.

更新

请不要被以下陈述所冒犯,我故意使用更严格的语言,以便强调你的第一个假设永远不应该是你正在使用的库有什么问题(除非你写的图书馆自己,你知道它有一个特定的问题)!

所以我们现在就把这个问题放在一边:既不ThreadPoolExecutor是Java库也不是Java库.这完全是你(错误)使用库造成了问题.Javmex有一个很好的教程,解释你所看到的确切情况.

可能有几个原因导致您填充队列的速度比清空队列要快:

  1. 添加执行任务的线程正在添加它们太快.
  2. 执行任务的时间太长.
  3. 你的队列太小了.
  4. 以上3种的任意组合.

还有很多其他原因,但我认为以上是最常见的.

我会给你一个带有无界队列简单解决方案,但它不能解决你对这个库的错误使用问题.因此,在我们指责Java库之前,让我们看一个简洁的示例来演示您遇到的确切问题.

更新2.0

以下是解决具体问题的其他几个问题:

  1. 队列已满时ThreadPoolExecutor阻塞?
  2. 如果ThreadPoolExecutor的commit()方法已经饱和,如何阻止它?

  • 我相信生产者消费模式必须由ThreadPoolExecutor API来处理,因为它负责将任务添加到阻塞队列中.如果队列已满,则应该等到队列清空然后应该添加任务.但不是等待它而是抛出异常,无法添加任务.所以ThreadPoolExecutor在使用阻塞队列时违反了生产者/消费者设计.请澄清我. (5认同)
  • @Kanagavelu,我不是 100% 确定他们为什么不只是在队列已满时阻塞在 `put` 上,但我很确定他们阻塞在 `take` 上。在 `take` 上阻塞确保线程不必使用任何类型的轮询来检查队列是否有数据(这被认为是一种低效的方法),而是工作线程等待/阻塞,直到它发出信号表明队列中有数据排队以便它可以出队。他们使用阻塞队列的另一个原因是因为它是线程安全的,因此在调用 `put`/`take` 时不需要用户方面的同步。 (2认同)

gka*_*mal 12

阻塞队列主要用于使用者(池中的线程).线程可以等待队列中的新任务可用,它们将自动被唤醒.简单的链表不会用于此目的.

在生产者端,默认行为是在队列已满时抛出异常.这可以通过实现自己的RejectedExceptionHandler轻松定制.在您的处理程序中,您可以获取队列并调用将阻塞的put方法,直到有更多空间可用.

但这不是一件好事 - 原因是如果这个执行器出现问题(死锁,处理速度慢),它会对系统的其余部分产生连锁反应.例如,如果从servlet调用execute方法 - 如果execute方法阻塞,那么所有容器线程都会被挂起,你的应用程序将停止运行.这可能是默认行为是抛出异常而不是等待的原因.此外,没有实现RejectedExceptionHandler来执行此操作 - 阻止人们使用它.

在调用线程中有一个选项(CallersRunPolicy),如果你想要进行处理,它可以是另一个选项.

一般规则是 - 最好不要处理一个请求,而不是整个系统崩溃.您可能想了解断路器模式.