我的线程如何将文件标记为正在使用,以便其他线程不会读取它?

时间:2014-09-22 17:54:49

标签: java multithreading file

我有一个监视目录的Java应用程序,当XML文件添加到该目录时,它会解析XML文件中的数据。

它将文件作为单线程应用程序工作,但我正在考虑将其设置为多线程,以便可以同时处理多个文件。

问题是,当线程#1找到一个文件并开始处理它时,如何将文件标记为“进行中”,以便线程#2不会尝试将其处理为?

我认为线程可以在文件开始处理后简单地重命名,myfile.xml.inprogress,然后myfile.xml.finished完成后。

但是,如果我这样做,两个线程是否可能同时看到该文件,并且两者都会尝试同时重命名它?

我可能还想运行此应用程序的两个实例来读取同一目录中的文件,因此我采取的任何路径都支持多个进程。

谢谢!

3 个答案:

答案 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)

  1. 您可以使用java.nio.channels.FileLockhttp://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileLock.html来同步不同进程之间的访问权限。
  2. 要在同一进程内运行的不同线程之间进行同步,您可以使用java.util.concurrent.locks.Lockhttp://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();
         }

    }
}