Java:实现多线程供应商/消费者管道,每种任务具有并行限制

Java:实现多线程供应商/消费者管道,每种任务具有并行限制,java,threadpool,producer-consumer,Java,Threadpool,Producer Consumer,我们需要异步处理不同类型的对象。每种类型的对象都使用API密钥进行处理 Semaphore key1Semaphore = new Semaphore(2); Semaphore key2Semaphore = new Semaphore(3); 每个API密钥对并发使用都有自己的限制(比如一个API密钥不超过5个并行会话) 我们有工作线程数的全局限制(CPU限制) 我们希望在工作线程限制内进行尽可能多的API调用 可能的解决办法: 2 tasks with KEY1 (max 2 sessi

我们需要异步处理不同类型的对象。每种类型的对象都使用API密钥进行处理

Semaphore key1Semaphore = new Semaphore(2);
Semaphore key2Semaphore = new Semaphore(3);
每个API密钥对并发使用都有自己的限制(比如一个API密钥不超过5个并行会话)

我们有工作线程数的全局限制(CPU限制)

我们希望在工作线程限制内进行尽可能多的API调用

可能的解决办法:

2 tasks with KEY1 (max 2 session) -\  total 3 workers
5 tasks with KEY2 (max 3 session) -/
3 tasks with KEY1 (max 1 session) & 3 workers
它们是:

可能的解决办法:

2 tasks with KEY1 (max 2 session) -\  total 3 workers
5 tasks with KEY2 (max 3 session) -/
3 tasks with KEY1 (max 1 session) & 3 workers
是:

执行顺序并不重要(但我们希望听到类似于先入先出的策略),最大吞吐量是最重要的

不清楚选择哪种实施战略


ThreadExecutor
和任何队列都是不够的,因为您需要知道
ThreadExecutor

当前使用了哪些API键,我不确定我是否答对了,但您需要的似乎是每个API键的一个

Semaphore key1Semaphore = new Semaphore(2);
Semaphore key2Semaphore = new Semaphore(3);
您可以通过调用
key1Semaphore.tryAcquire()
来检查
key1Semaphore
是否有许可证,如果有许可证,可以获取许可证。这是非阻塞的,因此如果它失败并返回false,您可以尝试从另一个API键获取信号量并从该API键提交任务

重要的是,在使用其中一个API键的任务结束时,信号量许可被释放回来

您可能需要一个额外的对象来与
wait()
notify()
同步,以便在任务完成时通知正在分派任务的主线程再次检查信号量

因此,从本质上讲,您的任务调度器将向您的
ExecutorService
提交5个任务(共3个工作人员),然后在其中一个信号量许可证被释放之前,它将无法再提交任何任务

当任务完成且许可证被释放时,调度程序将收到通知,因此它将取消等待的阻塞,并再次按顺序检查信号量,并将任务提交给
执行器服务


这个解决方案有点偏向于第一个API键,但是您可以通过检查每个键的任务长度并更公平地分配它们来进一步改进它。您甚至可以旋转索引,这样每次循环都会将索引增加1,这样第一次从API键1开始,第二次从API键2开始,等等。

我可能会创建一个服务

  • 单个
    队列
    ,包含由任务和相应键组成的条目
  • 带有键的
    Map
    ,以及该键的已运行线程(
    Map
    ),以及
  • 具有全局允许的线程计数的
    ThreadPoolExecutor
如果全局线程计数已满且任务已提交,则将其置于队列末尾

如果全局线程计数未满,则检查请求密钥对应的映射值以确定密钥线程限制;如果到达,任务将被放回队列,否则将提交给执行器服务

“提交到executor服务”不会直接提交任务,但会增加关键线程数,并将任务包装成一个
Runnable
,它将额外增加1。减少映射和2中的关键线程数。触发队列的重新评估,以便提交新任务(如果适用)


还可以在
BlockingQueue
中创建“每个键的活动计数”逻辑,该逻辑将返回
first()
下一个元素,该元素包含尚未达到最大计数的键的任务,并将其作为管理队列传递给
ThreadPoolExecutor
构造函数;但我相信这会破坏队列契约,使用起来也不完全安全。

为什么不在每个密钥上使用一个执行器,并在前面使用一个多路复用器,将每个任务分配给正确的执行器?我有200个API密钥,并且对CPU有限制。我不能仅仅用5个线程创建200个线程池,然后根据API密钥选择一个线程池。。。从两个方面来看,任务都很有趣。哦,所以您基本上想要一个具有全局线程数的主执行器服务,根据给定键的多少任务已经处于活动状态,使用一种策略来填充它?您可以看到我所担心的一切。)在简单情况下,队列用作延迟任务的临时存储,但在我的情况下,
next()
方法应该知道线程执行器的当前状态。问题是我不能使用线程池、执行器或队列的任何高级实现,需要从低级块构建解决方案。我考虑使用相同的方法,围绕原始任务向
Runnable
添加周围逻辑,并重用
ThreadPoolExecutor
。至少这样我就不用实现多线程代码了。棘手的部分是将任务返回到队列(自引用可能会导致“振荡”/临时100%CPU负载循环)。@gavenkoa是的,在我确定可以提交任务之前,我不会将任务从队列中取出,即迭代直到找到有效的任务,并将其删除。我想,这确实打消了使用队列的初衷。