具有多个限制的线程池

时间:2012-02-02 21:49:05

标签: java multithreading scala concurrency pool

我想要一个提供最多X个线程来处理任务的线程池,到目前为止没问题。但是,每个提交的任务都可以指定一个特定限制的IO目标(比如Y)。

因此,提交的IOTask会返回限制为4(Y)的目标“google.com”,并且池的全局限制为16(X)。我想提交10个google.com-tasks,其中只有4个并行处理,并且该池有12个线程可用于其他任务。

我怎样才能做到这一点?

6 个答案:

答案 0 :(得分:3)

实现此功能并不简单,因为您需要为每个目标设置单独的队列(因此​​等待代码变得更加复杂),或者您可以从中跳过一个容量的目标(导致性能开销) )。您可以尝试扩展ExecutorService来实现此目的,但扩展似乎并非易事。

更新的答案/解决方案:

在考虑了这一点之后,阻塞问题最简单的解决方案是同时拥有一个阻塞队列(按照正常情况)和一个队列映射(每个目标一个队列,以及一个每个目标的可用线程数)。在从常规阻塞队列中提取任务之后,队列映射仅用于已执行的任务(由于已为该目标运行的线程太多)。

因此执行流程如下所示:

  1. 通过调用代码提交任务(具有特定目标)。
  2. 任务被放入阻塞队列(可能由您自己的包含目标信息的任务类包装在这里)。
  3. 线程(来自线程池)正在等待阻塞队列(通过take())。
  4. 线程接受提交的任务。
  5. 线程在锁定时同步。
  6. thread检查该目标的可用计数。
  7. 如果可用计数> 0

    • 然后线程将count减少1,释放锁定,运行任务。
    • 否则线程将任务放入目标到任务队列的映射中(此映射是传递的任务映射),释放锁定,然后返回等待阻塞队列。
  8. 当一个线程完成一个任务的执行时:

    • 同步锁定。
    • 检查刚刚执行的目标的计数。
    • 如果计数== 0
      • 然后检查传递的任务图以查找此目标的任何任务(如果存在),然后释放锁定并运行它。
    • 如果count不为0或者同一目标的任务没有在传递的映射/队列上,则增加可用计数(对于该目标),释放锁定,然后返回等待阻塞队列。 / LI>
  9. 此解决方案可避免任何重大的性能开销或仅具有单独的线程来管理队列。

答案 1 :(得分:2)

以更具体的方式思考一些答案。

  1. 您需要自己的BlockingQueue,它可以分离不同类型的任务,并根据内部计数器返回所需的Runnable。

  2. 扩展ThreadPoolExecutor并实现beforeExecute和afterExecute。

  3. 当调用beforeExecute时,如果Runnable是X类型,它将在队列中递增一个计数器。当调用afterExecute时,它会减少该计数器。

    在您的队列中,您将根据计数器的值返回相应的Runnable,我相信take方法是您执行此操作的地方。

    这里有一些同步问题需要完全考虑,以确保计数器永远不会超过4.不幸的是,一旦你在beforeExecute之前它太晚了但是能够简单地知道在一个任务中运行了多少任务给定时间可能会让你开始。

答案 2 :(得分:2)

您可以在自定义类中包装两个ExecutorService实例,并按如下方式手动管理任务提交:

class ExecutorWrapper {

    private ExecutorService ioExec = Executors.newFixedThreadPool(4);
    private ExecutorService genExec = Executors.newFixedThreadPool(12);

    public Future<?> submit(final IOTask task) {
        return ioExec.submit(task);
    }

    public Future<?> submit(final Runnable task) {
        return genExec.submit(task);
    }
}

interface IOTask extends Runnable {}

这允许您使用4个线程来执行IO操作,并让其他12个线程为其他任务提供服务。

答案 3 :(得分:1)

嗯......我担心退出的ExecutorService不允许这样的细粒度控制。您可能需要扩展ExecutorService类以自己添加此功能,或者使用两个单独的固定线程池,一个容量为4,另一个容量为12。

答案 4 :(得分:1)

这样做的想法可能是扩展ExecutorService并在你的类中有两个ThreadPools,一个容量为4,另一个容量为12。

然后实现您需要的方法,并根据提交的IOTasks,您可以将任务指向您希望它转到哪个池。

答案 5 :(得分:1)

使用计数器来计算总线程数和HashMap,它计算当前尝试访问站点X的线程数。当你想要启动一个新线程时,调用一个synchronized方法来检查等待(while循环中的wait())直到哈希映射中的线程数小于4,线程总数小于16.然后递增两个计数器并启动线程。当线程完成时,它应该调用第二个同步方法,该方法递减计数器并调用notify()