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

时间:2019-04-04 08:52:29

标签: java threadpool producer-consumer

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

每个API密钥在并发使用上都有自己的限制(例如,一个API密钥的并行会话数不得超过5个)。

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

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

可能的解决方案:

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

是:

1. worker1: KEY2, worker2: KEY2, worker3: KEY2 (in queue: 2x KEY1, 2x KEY2)
2. worker1: KEY1, worker2: KEY2, worker3: KEY2 (in queue: 1x KEY1, 3x KEY2)
3. worker1: KEY1, worker2: KEY1, worker3: KEY2 (in queue: 4x KEY2)

可能的解决方案:

3 tasks with KEY1 (max 1 session) & 3 workers

是:

1. worker1: KEY1, worker2: IDLE, worker3: IDLE, (in queue 2x KEY1)

执行顺序无关紧要(但我们希望像政策一样先进先出),最大吞吐量是最重要的。

不清楚选择哪种实施策略。

ThreadExecutor具有任何队列是不够的,因为您需要知道ThreadExecutor当前正在使用哪些API密钥。

2 个答案:

答案 0 :(得分:1)

我不确定问题是否正确,但是您需要为每个API密钥准备一个Semaphore

Semaphore key1Semaphore = new Semaphore(2);
Semaphore key2Semaphore = new Semaphore(3);

您可以通过致电key1Semaphore来检查key1Semaphore.tryAcquire()是否获得许可,并获得许可。这是非阻塞的,因此如果失败并返回false,则可以尝试从另一个API密钥获取信号并从中提交任务。

重要的是,在使用API​​密钥之一完成任务后,必须释放信号量许可。

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

因此,从本质上讲,您所获得的是任务调度程序将向5个工作人员中的ExecutorService提交5个任务,然后在释放其中一个信号量许可之前,它将无法再提交任何内容。 / p>

当任务完成并且释放了许可时,调度员会收到通知,因此可以取消等待,然后再次检查信号量,然后将任务提交给ExecutorService

该解决方案偏向于第一个API密钥,但是您可以通过检查每个密钥的任务长度并更公平地分配它们,来进一步完善它。您甚至可以旋转索引,以便在每个循环中将索引递增1,这样第一次是从API KEY 1开始,第二次是从API KEY 2等。

答案 1 :(得分:1)

我可能会创建一个维护服务

  • 单个Queue,其中包含由任务和相应的键组成的条目,
  • 具有密钥和该密钥的已运行线程(Map)的Map<String,AtomicInteger>
  • 具有全局允许的线程数的ThreadPoolExecutor

如果全局线程计数已满,并且已提交任务,则将其放入队列的末尾。

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

“提交给执行者服务”将不会直接提交任务,而是增加密钥线程数,并将任务包装到Runnable中,这将另外1.减少映射中的密钥线程数和2触发队列的重新评估,以便在适用时提交新任务。

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