The cleanest solution would be to introduce a special task object that makes a consumer terminate. Such a special object is often called poison pill. If there are n consumer threads put n poison pills to the queue when all the real tasks are read from the file. The main thread my end after putting n poison pills to the queue. After each consumer has terminated all threads are finished and the JVM process will terminate.
Another and less elegant solution would be to check if there are still tasks and then signal the consumer threads that this event has happend though a shared boolean
variable (called end
in the following example). This variable has to be marked volatile
in order to make the change from the main thread visible to the consumer threads. Otherwise one must rely on passing other memory barriers such as synchronized
. With this solution the consumers may not use take()
in order to fetch tasks from the queue because this would block them if no tasks is available (not sure if this could happen in your processing).
import java.util.concurrent.LinkedBlockingQueue;
public class ProducerConsumer {
static class Task {} // just something to represent the task
static volatile boolean end = false; // signal the consumers that they can stop
public static void main(String[] args) {
var tasks = new LinkedBlockingQueue<Task>();
(new Thread(() -> {
while (!end) {
var t = tasks.poll();
if (t != null) {
// process t
}
}
})).start(); // Consumer, several of them
// fill tasks from file...
// may be started after all tasks are read from the file
// check every second if there are still tasks in the queue
while (tasks.size() > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {}
}
end = true;
}
}