给定 Executor 服务具有固定的线程池,是否可以保证任务到线程的确定性分配?更准确地说,假设只有两个线程,即 pool-thread-0 和 pool-thread-1,并且有 2 个要执行的任务的集合。我希望实现的是前一个线程始终执行第一个线程,而后者处理剩余的线程。
这是一个例子:
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = newFixedThreadPool(2,
new ThreadFactoryBuilder().setNameFormat("pool-thread-%d").build());
for (int i = 0; i < 5; i++) {
List<Callable<Integer>> callables = ImmutableList.of(createCallable(1), createCallable(2));
executorService.invokeAll(callables);
}
}
public static Callable<Integer> createCallable(final int task) {
return new Callable<Integer>() {
@Override
public Integer call() throws Exception {
currentThread().sleep(1000);
System.out.println(Thread.currentThread().getName() + " executes task num: " + task);
return task;
}
};
}
我的机器的示例输出:
pool-thread-0 executes task num: 1
pool-thread-1 executes task num: 2
pool-thread-0 executes task num: 2
pool-thread-1 executes task num: 1
pool-thread-0 executes task num: 2
pool-thread-1 executes task num: 1
pool-thread-0 executes task num: 2
pool-thread-1 executes task num: 1
pool-thread-0 executes task num: 1
pool-thread-1 executes task num: 2
简而言之,我希望确保 pool-thread-0 始终执行第一个任务。任何帮助将不胜感激!
ExecutorService 并非旨在为其 Callable/Runnable 提供“线程亲和力”。有人可能会说“这就是重点”,API 的作用是让程序员处理工作描述(Callable),而不是线程处理。
您的设计,“有一个与每个线程关联的随机数据生成器”不适合 ExecutorService,我认为有以下三个原因:
您无法控制将创建(或销毁!)以及何时创建(或销毁!)哪些线程(如果一个线程崩溃了怎么办?池将重新创建它,但它将获得什么随机生成器?)。因此,我们无法推断出一种可靠的方式来表示“这个线程”有“这个生成器”,更不用说“第二个”线程有“这个生成器”了,因为甚至可能没有第二个线程(如果每个任务都如此之快怎么办?他们的治疗速度比你派遣他们的速度还要快?)。
您无法控制何时执行哪些任务。好吧...使用 Executors.newFixedThreadPool,您可以按照提交顺序分派它们,但据您所知,操作系统调度程序可能会将所有优先级赋予线程 1,最终将完成所有工作,并且线程 2 将不执行任何操作(可以是两者之间的任意比例)。
将“数据生成器”传递给线程的唯一方法是重写执行程序服务的 ThreadFactory。否则,您无法访问线程实例(除了运行时可调用本身之外)。因此,要将特定的生成器关联到特定的线程,您必须知道当前正在创建哪个线程号,如果您正在计算线程数,这很容易,但如果您想知道这是什么 Callable,则很困难线程的目的是(参见第 2 点)。
因此,我强烈建议您定义一些其他方式将工作单元与数据生成器关联起来,因为“线程实例”通常不可靠 - 至少通过执行器服务不可靠。
例如。当你说
我需要保证它们处理的线程和数据的组合是可重复的。
据我所知,您将始终分派一定数量的 Callable,并且您需要每个 Callable 来处理由特定生成器发出的特定数据集。假设我们有给定数量的任务和 3 个生成器,task(N) 将使用生成器N%3
.
为了使结果可重复,您还需要使用同一生成器的任务不要同时执行(您希望通过线程亲和力实现什么?)。
有一定数量的模式可以实现这一点。
1 是:重构为生产者/消费者(反之亦然)
在你的执行器服务中创建 3 个任务,每个任务都监听一个BlockingQueue
(其私人等候名单)并拥有自己的私人发电机。这些是消费者。
让你的主线程成为生产者:当它创建工作单元(曾经是一个Callable
在你原来的设计中)编号为N,将其调度到编号为N%3的等待队列。就是这样:每个消费者将按照您希望的顺序按顺序接收自己的数据以进行计算。你们已经达到了“缘分”。
2是:让任务自己派发任务。 (用 hacky 的方式来做)
首先,重构您的可调用对象以链接到它们需要使用的生成器。
然后,在主线程上,构建要为每个生成器运行的任务列表。
从主线程为每个生成器分派第一个任务。
在每个可调用对象的末尾,使可调用对象从其列表中调度下一个工作单元。
不过,请注意不要“将您锁定”,如果您从可调用对象分派可调用对象,请不要等待结果,因为这将阻止可调用对象完成,进而阻止新分派的执行。这是一个僵局。
3是:与2相同,效率较低,但风险较小
不要从可调用对象内部分派可调用对象,而是通过等待 future 来仅从主线程分派。
使用这两种方式中的任何一种,都不能保证哪些任务将首先完成或最后完成,但可以保证您分派的工作单元可预测地与您控制的数据生成器相关联,并且它们将按照您分派的顺序执行他们。希望这已经足够了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)