You should use a Producer-Consumer
pattern. Have one thread to listen for changes in the files and pass off that work to other threads.
You can use a BlockingQueue
for this to make the code very simple.
First you need two classes, a producer:
class Producer implements Callable<Void> {
private final BlockingQueue<Path> changedFiles;
Producer(BlockingQueue<Path> changedFiles) {
this.changedFiles = changedFiles;
}
@Override
public Void call() throws Exception {
while (true) {
if (something) {
changedFiles.add(changedFile);
}
//to make the thread "interruptable"
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
} catch (InterruptedException ex) {
break;
}
}
return null;
}
}
And a Consumer
:
class Consumer implements Callable<Void> {
private final BlockingQueue<Path> changedFiles;
Consumer(BlockingQueue<Path> changedFiles) {
this.changedFiles = changedFiles;
}
@Override
public Void call() throws Exception {
while (true) {
try {
final Path changedFile = changedFiles.take();
//process your file
//to make the thread "interruptable"
} catch (InterruptedException ex) {
break;
}
}
return null;
}
}
So, now create an ExecutorService
, submit one Producer
and as many consumers as you need:
final BlockingQueue<Path> queue = new LinkedBlockingDeque<>();
final ExecutorService executorService = Executors.newCachedThreadPool();
final Collection<Future<?>> consumerHandles = new LinkedList<>();
for (int i = 0; i < numConsumers; ++i) {
consumerHandles.add(executorService.submit(new Consumer(queue)));
}
final Future<?> producerHandle = executorService.submit(new Producer(queue));
So you guarantee that only one file is being worked on at a time because you control that yourself. You also do so with minimum synchronisation.
It might be worthwhile the Consumer
also reading the file to remove the the shared disc IO that will happen otherwise - this will likely slow the system down. You could also add another Consumer
that writes changed files at the other end to completely eliminate shared IO.
To shutdown the system simply call:
executorService.shutdownNow();
executorService.awaitTermination(1, TimeUnit.DAYS);
Because your workers are interruptible this will bring down the ExectuorService
once tasks currently in progress have finished.