Java 工作人员既是生产者又是消费者的线程池
我有一个无限的作业队列,可以异步处理。每个作业的处理可能会也可能不会触发为此队列创建新作业 我希望有一个由多个工作线程组成的池,从该队列中获取项目并并行处理它们,直到队列都为空和所有工作线程都处于空闲状态,等待队列上的新作业(因为忙碌的工作线程最终可能会向队列中添加新作业) 是否有一种使用Java 工作人员既是生产者又是消费者的线程池,java,multithreading,concurrency,producer-consumer,java.util.concurrent,Java,Multithreading,Concurrency,Producer Consumer,Java.util.concurrent,我有一个无限的作业队列,可以异步处理。每个作业的处理可能会也可能不会触发为此队列创建新作业 我希望有一个由多个工作线程组成的池,从该队列中获取项目并并行处理它们,直到队列都为空和所有工作线程都处于空闲状态,等待队列上的新作业(因为忙碌的工作线程最终可能会向队列中添加新作业) 是否有一种使用java.util.concurrent实现的方法,我可以用它来解决工人也是生产者的这个特殊问题?目前尚不清楚API是否直接支持这种场景 特别是,我希望能够检测终止条件,即,当没有更多的作业可用时(空作业队列)
java.util.concurrent
实现的方法,我可以用它来解决工人也是生产者的这个特殊问题?目前尚不清楚API是否直接支持这种场景
特别是,我希望能够检测终止条件,即,当没有更多的作业可用时(空作业队列),将不会产生更多的作业(所有空闲工作线程)
编辑
Nam San下面的答案似乎是最优雅的方法,它基本上归结为跟踪提交作业的数量与完成作业的数量,并使用这些数量相等的情况作为终止条件
我已经使用java.util.concurrent
实现实现了一个完整的示例,它扩展了ThreadPoolExecutor
来实现这一点,并专门处理作业队列,以接受以特定方式排序的可比较的
实例
- :一个自定义执行器,它扩展了
,但有额外的方法来执行可能创建新作业的作业,还有一个新的等待方法,等待所有提交的作业完成ThreadPoolExecutor
- :一个可比较的可运行作业的示例,该作业可能会创建新作业以提交给
测试执行器
- :包含使用带有
测试执行器的
实例运行示例的主要方法工作单元
您可以使用
AtomicInteger
或类似工具来记录当前活动的工作人员数量,如果您想等待所有工作人员都静止下来,也可以使用CountDownLatch
。我已经看到了一些解决此类问题的不同方法
一种方法是在主线程中仍然使用poll
作为阻塞调用,就像在代码中一样,但将工作线程中的“虚拟”对象排队,以唤醒主线程,否则它可能会永远等待。例如,任何未向队列添加更多项而完成的工作线程都应提交一个虚拟作业,主线程会识别并忽略该作业(它只用于唤醒主线程)。通过跟踪活动作业的数量,您可以创建更少的虚拟对象,从而减少“虚假唤醒”,但代价是增加一些复杂性—只有最后一个作业需要添加虚拟对象
另一种方法是等待另一个对象。例如,任何旧的对象
都可以。在此对象上设置主线程wait()
。然后作业在完成时使用Object.notify()
唤醒此线程。同样,通过计数,您可以减少所需通知的数量
最优雅的解决方案可能是使用信号灯
。基本上,信号量的值将是“飞行中作业+队列项目”数量的负数。当作业从队列中拾取一个项目时,此值不会更改(因为正在运行的作业上升一个,而队列项目下降一个),但每个作业都应该为其添加的每个作业调用reducePermits(),并在完成之前调用一个release()
然后主线程可以在工作期间阻塞acquire()
。当它醒来时,一切都完成了(因为在飞行中+排队工作为零)。您需要启动另一个线程来实际执行poll
调用并添加作业(当前由主线程完成),当主线程上的acquire
返回时,此工作线程可以关闭。但是,让现有的workerpoll()
自己完成可能更简单。那么你根本不需要这个传递函数
事实上,使用
信号量解决方案
时,为什么不完全取消队列,使用内置在执行器中的队列呢?也就是说,让工人通过executor.submit(newJob(nextJob))
提交新工作?在内部,执行器线程无论如何都在从阻塞队列中提取工作,因此在拥有显式外部队列时存在一些重复。年前,我不得不做一些类似的事情,但使用有界堆栈。我将分享一个可能的解决方案:
idle_thread = MAX_THREAD;
do
{
if(queue != empty) // If thread have work to do
{
idle_threads--; // Count this threads was a worker
flag = true;
while(queue != empty) // Until queue have work
{
synchronized(this)
{
// task = take_out_of_queue;
}
}
}
if(flag) // This flag must to be local to each thread, it is use to insure
{ // that threads will count this only one time for each time
// the queue got empty
synchronized(this)
{
if(flag == false)
idle_threads++; // Count thread as a idle one
flag = false;
}
}
if(idle_threads == MAX_THREADS) out = true; // When all threads are idle stop the work loop
} while(!out)
请看我在上面的帖子,它满足了大多数要求。但它并没有在期货和可赎回债券上实现。我得想清楚。每项任务都没有得到重视。没有产生结果和异常。这只是一种并行和递归的文件扫描方式。下面的代码演示了如何使用
执行器周围的包装器类来计算提交的作业数,并将其与完成的作业数进行比较,以实现所需目标。请注意,您的任务必须调用包装类的execute
方法,并且决不能直接调用底层的executer
。如果需要,扩展下面的包装来包装ExecutorService
的“submit”方法应该很简单
public class ExampleExecutor {
private final Executor executor;
private long submitCount = 0;
private long doneCount = 0;
public ExampleExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(Collection<Runnable> commands) {
for (Runnable command : commands) {
execute(command);
}
}
public synchronized void execute(final Runnable command) {
submitCount ++;
executor.execute(new Runnable() {
public void run() {
try {
command.run();
} finally {
synchronized (ExampleExecutor.this) {
doneCount++;
if (doneCount == submitCount) {
ExampleExecutor.this.notifyAll();
}
}
}
}
});
}
public synchronized void awaitCompletion() throws InterruptedException {
while (doneCount != submitCount) {
this.wait();
}
}
}
公共类示例执行器{
私人最终执行人;
私有长submitCount=0;
私有长doneCount=0;
公共示例执行器(执行器执行器){
this.executor=执行人;
}
公共同步作废执行(收集)
public class Test {
static class Task implements Runnable {
private final String id;
private final long repetitions;
private final long respawnSize;
private final ExampleExecutor executor;
public Task(String id, long repetitions, long respawnSize, ExampleExecutor executor) {
this.id = id;
this.repetitions = repetitions;
this.respawnSize = respawnSize;
this.executor = executor;
}
public void run() {
for (int i = 0; i < respawnSize; i ++) {
// Spawning new sub tasks
executor.execute(new Task(id + "-" + i, repetitions/2, 0, null));
}
double sum = 0;
for (int i = 0; i < repetitions; i++) {
sum += Math.sin(i);
}
System.err.println(id + " completed at " + System.currentTimeMillis() + " with sum=" + sum);
}
}
public static void main(String argv[]) throws InterruptedException {
ExampleExecutor executor = new ExampleExecutor(Executors.newFixedThreadPool(2));
executor.execute(new Task("0", 2000000, 100, executor));
System.err.println("main thread awaits completion");
executor.awaitCompletion();
System.err.println("main thread recieved completion event");
}
}