如何使用Java锁定文件(如果可能)

133

我有一个Java进程,使用FileReader打开一个文件。如何防止另一个(Java)进程打开此文件,或者至少通知第二个进程文件已经被打开?这会自动使得第二个进程在文件已经被打开时出现异常(从而解决了我的问题),还是我需要在第一个进程中显式地使用某种标志或参数来打开它?

澄清一下:

我有一个Java应用程序,列出一个文件夹并打开列表中的每个文件进行处理。它按顺序处理每个文件。每个文件的处理包括读取它并根据内容进行一些计算,大约需要2分钟。我还有另一个Java应用程序,它执行相同的操作,但是写入文件而不是读取文件。我想要能够同时运行这些应用程序,因此场景如下:ReadApp列出文件夹并找到文件A、B、C。它打开文件A并开始读取。WriteApp列出文件夹并找到文件A、B、C。它打开文件A,看到它已经打开(通过异常或任何其他方式),然后转到文件B。ReadApp完成文件A并继续到B。它看到它是打开的并继续到C。关键是当ReadApp正在读取同一文件或反之亦然时,WriteApp不要写入。它们是不同的进程。


13
你的意思是“process”是指两个JVM中的process还是线程(同一个JVM)?对答案的影响至关重要。 - Stu Thompson
请查看此处显示解决方案的示例代码: https://dev59.com/a3E95IYBdhLWcg3wi-Yc#58871479 - Davi Cavalcanti
8个回答

124

FileChannel.lock可能是您想要的。

try (
    FileInputStream in = new FileInputStream(file);
    java.nio.channels.FileLock lock = in.getChannel().lock();
    Reader reader = new InputStreamReader(in, charset)
) {
    ...
}

(免责声明:代码未编译且肯定未经测试。)

请注意“平台依赖性”部分,在FileLock的API文档中。


28
更重要的是,要理解该锁定适用于JVM,而不适合用于在单个JVM内由各个线程访问文件时进行锁定。 - Stu Thompson
11
你需要一个可写的流(例如 FileOutputStream)。 - Javier
1
@Javier 你试过吗?我没有尝试过。API文档中没有明确说明这是必需的。FileOutputStream对于Reader来说并没有太大用处。 - Tom Hawtin - tackline
19
是的,我尝试了一下,它会抛出 NonWritableChannelException 异常,因为 lock() 试图获取独占锁,但这需要写入权限。如果你有一个输入流,你可以使用 lock(0L, Long.MAX_VALUE, false) 来获取共享锁,只需要读取权限。如果你想要在读取时获得独占锁,你也可以使用以读写模式打开的 RandomAccessFile ...但这将禁止并发读取。 - Javier
7
@Javier 我认为你想表达的是lock(0L, Long.MAX_VALUE, true),而不是lock(0L, Long.MAX_VALUE, false)。那里的最后一个参数是 boolean shared。http://docs.oracle.com/javase/8/docs/api/java/nio/channels/FileChannel.html#lock-long-long-boolean- - john sullivan
显示剩余2条评论

65

不要使用java.io包中的类,而应该使用java.nio包。后者有一个FileLock类,您可以将锁应用于FileChannel

 try {
        // Get a file channel for the file
        File file = new File("filename");
        FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

        // Use the file channel to create a lock on the file.
        // This method blocks until it can retrieve the lock.
        FileLock lock = channel.lock();

        /*
           use channel.lock OR channel.tryLock();
        */

        // Try acquiring the lock without blocking. This method returns
        // null or throws an exception if the file is already locked.
        try {
            lock = channel.tryLock();
        } catch (OverlappingFileLockException e) {
            // File is already locked in this thread or virtual machine
        }

        // Release the lock - if it is not null!
        if( lock != null ) {
            lock.release();
        }

        // Close the file
        channel.close();
    } catch (Exception e) {
    }

顺便提一下,我正在从这个提示https://dev59.com/t3VD5IYBdhLWcg3wQZUg#35885中写入当前pid到锁文件中,这样在新实例上就可以读取它了! - Aquarius Power
1
这个看起来不错,但是它不起作用。每次我都会收到OverlappingFileLockException的错误,即使文件根本不存在。 - Gavriel
1
如果按照示例中的方式在 lock 后调用 tryLock,就会出现问题。 - Igor Vuković
对于锁定而言,通常非常关键的是要在 finally 中进行锁释放操作,以确保即使出现异常情况也能够释放。如果您能够解决这个问题,那就太好了,这样可以避免产生错误。好的,接受的答案中使用资源管理器语句(try with resources)可能通常甚至更好。 - Hans-Peter Störr

19
如果您可以使用Java NIO(JDK 1.4或更高版本),那么我认为您正在寻找java.nio.channels.FileChannel.lock()。

FileChannel.lock()


6
可能。这取决于 OP 所指的“process”的含义。“文件锁是代表整个Java虚拟机持有的。它们不适合用于控制同一虚拟机内多个线程对文件的访问。” - Stu Thompson
@Stu:我知道你很久以前已经回答了这个问题,但我希望你能详细说明一下你所说的“文件锁是代表整个Java虚拟机持有的,它们不适合用于控制同一虚拟机中多个线程对文件的访问”。 - Thang Pham
4
他在引用文档:http://download.oracle.com/javase/6/docs/api/java/nio/channels/FileLock.html。这意味着该锁定对线程是不可见的,但会影响其他进程。 - Artur Czajka
1
@Harry:更进一步地说,想象一下您正在使用Java为Tomcat提供网站服务。您可能有很多线程,每个线程为Web浏览器提供一个请求。但是,它们都控制着相同的文件锁定机制,就像厨房里太多的厨师一样。一个请求可能在第二个请求中间完成,突然间,您的文件被“解锁”,而您仍然在进行某些操作,然后某个cron作业之类的其他进程可能会将其锁定,然后您意外地失去了锁定,您的请求无法完成... - Darien


5
这可能不是您要寻找的,但为了从另一个角度解决问题...。
这两个Java进程可能希望在同一应用程序中访问相同的文件吗?也许您可以通过单个同步方法(或者更好地使用JSR-166)过滤对文件的所有访问?这样,您就可以控制对文件的访问,甚至可以排队访问请求。

3
两个进程无法使用同步,只有在同一个进程中的两个线程才能使用。 - user207421

3

使用RandomAccessFile,获取其通道,然后调用lock()方法。由输入或输出流提供的通道没有足够的权限来正确锁定文件。请务必在finally块中调用unlock()方法(关闭文件不一定会释放锁定)。


你能详细说明一下吗?我的意思是,相比于流锁,随机访问文件锁更好还是更安全到什么程度呢? - Paralife
以下是一个简单示例的链接:[link] - Touko
Paralife - 抱歉回复晚了 - 刚刚看到你的问题。流中的锁将是读锁(用于输入流)和独占、完整通道写锁(用于输出流)。我的经验是,RAF 中的锁允许更细粒度的控制(即,您可以锁定文件的部分内容)。 - Kevin Day

1

如果您使用winscp或ftp进行传输,请在Unix上使用此方法:

public static void isFileReady(File entry) throws Exception {
        long realFileSize = entry.length();
        long currentFileSize = 0;
        do {
            try (FileInputStream fis = new FileInputStream(entry);) {
                currentFileSize = 0;
                while (fis.available() > 0) {
                    byte[] b = new byte[1024];
                    int nResult = fis.read(b);
                    currentFileSize += nResult;
                    if (nResult == -1)
                        break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("currentFileSize=" + currentFileSize + ", realFileSize=" + realFileSize);
        } while (currentFileSize != realFileSize);
    }

1
以下是一个示例代码片段,用于锁定文件,直到JVM处理完成。
 public static void main(String[] args) throws InterruptedException {
    File file = new File(FILE_FULL_PATH_NAME);
    RandomAccessFile in = null;
    try {
        in = new RandomAccessFile(file, "rw");
        FileLock lock = in.getChannel().lock();
        try {

            while (in.read() != -1) {
                System.out.println(in.readLine());
            }
        } finally {
            lock.release();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Java 11发布于2011年。 - Arneball

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接