我有一个监视目录的Java应用程序,当XML文件添加到该目录时,它会解析XML文件中的数据。
它将文件作为单线程应用程序工作,但我正在考虑将其设置为多线程,以便可以同时处理多个文件。
问题是,当线程#1找到一个文件并开始处理它时,如何将文件标记为“进行中”,以便线程#2不会尝试将其处理为?
我认为线程可以在文件开始处理后简单地重命名,myfile.xml.inprogress
,然后myfile.xml.finished
完成后。
但是,如果我这样做,两个线程是否可能同时看到该文件,并且两者都会尝试同时重命名它?
我可能还想运行此应用程序的两个实例来读取同一目录中的文件,因此我采取的任何路径都支持多个进程。
谢谢!
答案 0 :(得分:2)
您应该使用Producer-Consumer
模式。有一个线程可以监听文件中的更改,并将该工作传递给其他线程。
您可以使用BlockingQueue
来使代码变得非常简单。
首先你需要两个类,一个制作人:
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;
}
}
和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;
}
}
所以,现在创建一个ExecutorService
,提交一个Producer
和尽可能多的消费者:
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));
因此,您保证一次只处理一个文件,因为您自己控制了该文件。您也可以通过最少的同步来完成此操作。
Consumer
也读取文件以删除将发生的共享光盘IO可能是值得的 - 这可能会降低系统速度。您还可以添加另一个Consumer
,在另一端写入已更改的文件,以完全消除共享IO。
要关闭系统,只需致电:
executorService.shutdownNow();
executorService.awaitTermination(1, TimeUnit.DAYS);
因为您的工作人员可以中断,所以当正在进行的任务完成后,这将导致ExectuorService
。
答案 1 :(得分:0)
java.nio.channels.FileLock
:http://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileLock.html来同步不同进程之间的访问权限。 java.util.concurrent.locks.Lock
:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Lock.html 答案 2 :(得分:0)
生产者消费者绝对是这里的想法。
Java 7
提供了WatchService,可以处理Producer
问题,即使使用起来很麻烦。
拥有所需池大小的ExecutorService
来处理Consumers
。
以下是它如何连线。
public class FolderWatchService {
private ExecutorService executorService = Executors.newFixedThreadPool(5);
public void watch() throws Exception {
Path folder = Paths.get("/home/user/temp");
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
folder.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
while(true) {
final WatchKey key = watchService.take();
if (key != null) {
for (WatchEvent<?> watchEvent : key.pollEvents()) {
WatchEvent<Path> event = (WatchEvent<Path>) watchEvent;
Path dir = (Path) key.watchable();
Path absolutePath = dir.resolve(event.context());
executorService.submit(new WatchTask(absolutePath));
}
key.reset();
}
}
}
}
public static void main(String[] args) throws Exception {
FolderWatchService folderWatchService = new FolderWatchService();
folderWatchService.watch();
}
}
class WatchTask implements Runnable {
private Path absolutePath;
WatchTask(Path absolutePath) {
this.absolutePath = absolutePath;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + absolutePath.toAbsolutePath());
try (BufferedReader reader = Files.newBufferedReader(absolutePath , StandardCharsets.UTF_8)) {
//Do read
reader.lines().forEach(line -> System.out.println(line));
} catch (IOException e) {
e.printStackTrace();
}
}
}