How to Lock a File in Java – 如何在Java中锁定一个文件

最后修改: 2020年 2月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

When reading or writing files, we need to make sure proper file-locking mechanisms are in place. This ensures data integrity in concurrent I/O based applications.

当读或写文件时,我们需要确保有适当的文件锁定机制。这可以确保基于I/O的并发应用中的数据完整性。

In this tutorial, we’ll take a look at various approaches to achieve this using the Java NIO library.

在本教程中,我们将看看使用Java NIO库来实现这一目标的各种方法

2. Introduction to File Locks

2.文件锁的介绍

In general, there are two types of locks:

一般来说,有两种类型的锁

    • Exclusive locks — also known as write locks
    • Shared locks — also referred to as read locks

Put simply, an exclusive lock prevents all other operations – including reads – while a write operation completes.

简单地说,在写操作完成时,独占锁会阻止所有其他操作–包括读–。

In contrast, a shared lock allows more than one process to read at the same time. The point of a read lock is to prevent the acquisition of a write lock by another process. Typically, a file in a consistent state should indeed be readable by any process.

相反,共享锁允许一个以上的进程同时进行读取。读锁的意义在于防止另一个进程获得写锁。通常情况下,一个处于一致状态的文件确实应该可以被任何进程读取。

In the next section, we’ll see how Java handles these types of locks.

在下一节,我们将看到Java如何处理这些类型的锁。

3. File Locks in Java

3.Java中的文件锁

The Java NIO library enables locking files at the OS level. The lock() and tryLock() methods of a FileChannel are for that purpose.

Java NIO库能够在操作系统层面上锁定文件。lock()tryLock()方法的FileChannel是为了这个目的。

We can create a FileChannel through either a FileInputStream, a FileOutputStream, or a RandomAccessFile. All three have a getChannel() method that returns a FileChannel.

我们可以通过FileInputStreamFileOutputStreamRandomAccessFile创建一个文件通道。所有这些都有一个getChannel()方法来返回一个FileChannel

Alternatively, we can create a FileChannel directly via the static open method:

另外,我们可以通过静态的open方法直接创建一个FileChannel

try (FileChannel channel = FileChannel.open(path, openOptions)) {
  // write to the channel
}

Next, we’ll review different options for getting exclusive and shared locks in Java. To learn more about file channels, check out our Guide to Java FileChannel tutorial.

接下来,我们将回顾在Java中获得独占锁和共享锁的不同选择。要了解有关文件通道的更多信息,请查看我们的Guide to Java FileChannel 教程。

4. Exclusive Locks

4.专用锁

As we’ve already learned, while writing to a file, we can prevent other processes from reading or writing to it by using an exclusive lock.

正如我们已经学过的,在向文件写入时,我们可以通过使用独占锁来防止其他进程对其进行读写

We get exclusive locks by calling lock() or tryLock() on the FileChannel class. We can also use their overloaded methods:

我们通过在FileChannel类上调用lock()tryLock()获得独占锁。我们也可以使用它们的重载方法。

  • lock(long position, long size, boolean shared)
  • tryLock(long position, long size, boolean shared)

In those cases, the shared parameter has to be set to false.

在这些情况下,shared参数必须设置为false

To get an exclusive lock, we must use a writable FileChannel. We can create it through the getChannel() methods of a FileOutputStream or a RandomAccessFile. Alternatively, as previously mentioned, we can use the static open method of the FileChannel class.  All we need is to set the second argument to StandardOpenOption.APPEND:

为了获得一个独占锁,我们必须使用一个可写的文件通道。我们可以通过FileOutputStreamRandomAccessFilegetChannel()方法创建它。另外,如前所述,我们可以使用FileChannel类的静态open方法。 我们只需要将第二个参数设置为StandardOpenOption.APPEND

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.APPEND)) { 
    // write to channel
}

4.1. Exclusive Locks Using a FileOutputStream

4.1.使用FileOutputStream的独占锁

FileChannel created from a FileOutputStream is writable. We can, therefore, acquire an exclusive lock:

FileOutputStream 创建的 FileChannel 是可写的。因此,我们可以获得一个独占锁。

try (FileOutputStream fileOutputStream = new FileOutputStream("/tmp/testfile.txt");
     FileChannel channel = fileOutputStream.getChannel();
     FileLock lock = channel.lock()) { 
    // write to the channel
}

Here, channel.lock() will either block until it obtains a lock, or it will throw an exception. For example, if the region specified is already locked, an OverlappingFileLockException is thrown. See the Javadoc for a complete list of possible exceptions.

在这里,channel.lock()将阻塞直到它获得一个锁,或者它将抛出一个异常。例如,如果指定的区域已经被锁定,则会抛出一个OverlappingFileLockException。请参阅Javadoc以获得可能出现的异常的完整列表。

We can also perform a non-blocking lock using channel.tryLock(). If it fails to get a lock because another program holds an overlapping one, then it returns null. If it fails to do so for any other reason, then an appropriate exception is thrown.

我们也可以使用channel.tryLock()进行非阻塞锁。如果它未能获得一个锁,因为另一个程序持有一个重叠的锁,那么它返回null。如果它因为其他原因而失败,那么就会抛出一个适当的异常。

4.2. Exclusive Locks Using a RandomAccessFile

4.2.使用RandomAccessFile的独占锁

With a RandomAccessFile, we need to set flags on the second parameter of the constructor.

有了RandomAccessFile,我们需要在构造器的第二个参数上设置标志

Here, we’re going to open the file with read and write permissions:

在这里,我们要用读写权限打开文件。

try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "rw");
      FileChannel channel = file.getChannel();
      FileLock lock = channel.lock()) {
    // write to the channel
}

If we open the file in read-only mode and try to write to its channel it will throw a NonWritableChannelException.

如果我们以只读模式打开文件,并试图向其通道写入,它将抛出一个NonWritableChannelException

4.3. Exclusive Locks Require a Writable FileChannel

4.3.独占锁需要一个可写的文件通道

As mentioned before, exclusive locks need a writable channel. Therefore, we can’t get an exclusive lock through a FileChannel created from a FileInputStream:

如前所述,独占锁需要一个可写通道。因此,我们不能通过一个从FileInputStream创建的FileChannel来获得一个独占锁。

Path path = Files.createTempFile("foo","txt");
Logger log = LoggerFactory.getLogger(this.getClass());
try (FileInputStream fis = new FileInputStream(path.toFile()); 
    FileLock lock = fis.getChannel().lock()) {
    // unreachable code
} catch (NonWritableChannelException e) {
    // handle exception
}

In the example above, the lock() method will throw a NonWritableChannelException. Indeed, this is because we’re invoking getChannel on a FileInputStream, which creates a read-only channel.

在上面的例子中,lock()方法将抛出一个NonWritableChannelException。事实上,这是因为我们在一个FileInputStream上调用了getChannel,它创建了一个只读的通道。

This example is just to demonstrate that we can’t write to a non-writable channel. In a real-world scenario, we wouldn’t catch and rethrow the exception.

这个例子只是为了证明我们不能写到一个不可写的通道。在真实世界的情况下,我们不会捕捉并重新抛出这个异常。

5. Shared Locks

5.共用锁

Remember, shared locks are also called read locks. Hence, to get a read lock, we must use a readable FileChannel.

记住,共享锁也被称为锁。因此,为了获得一个读锁,我们必须使用一个可读的文件通道

Such a FileChannel can be obtained by calling the getChannel() method on a FileInputStream or a RandomAccessFile. Again, another option is to use the static open method of the FileChannel class. In that case, we set the second argument to StandardOpenOption.READ:

这样的文件通道可以通过调用FileInputStreamRandomAccessFile上的getChannel()方法获得。同样,另一个选择是使用FileChannel类的静态open方法。在这种情况下,我们将第二个参数设置为StandardOpenOption.READ

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
    FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {
    // read from the channel
}

One thing to note here is that we chose to lock the entire file by calling lock(0, Long.MAX_VALUE, true). We could also have locked only a specific region of the file by changing the first two parameters to different values. The third parameter has to be set to true in the case of a shared lock.

这里需要注意的是,我们选择通过调用lock(0, Long.MAX_VALUE, true)锁定整个文件。我们也可以通过改变前两个参数的值,只锁定文件的一个特定区域。在共享锁的情况下,第三个参数必须被设置为true

To keep things simple, we’ll be locking the whole file in all the examples below, but keep in mind we can always lock a specific region of a file.

为了保持简单,我们将在下面的所有例子中锁定整个文件,但请记住,我们总是可以锁定一个文件的特定区域。

5.1. Shared Locks Using a FileInputStream

5.1.使用FileInputStream的共享锁

FileChannel obtained from a FileInputStream is readable. We can, therefore, get a shared lock:

FileInputStream获得的FileChannel是可读的。因此,我们可以获得一个共享锁。

try (FileInputStream fileInputStream = new FileInputStream("/tmp/testfile.txt");
    FileChannel channel = fileInputStream.getChannel();
    FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {
    // read from the channel
}

In the snippet above, the call to lock() on the channel will succeed. That’s because a shared lock only requires that the channel is readable. It’s the case here since we created it from a FileInputStream.

在上面的片段中,对通道的lock()的调用将会成功。这是因为一个共享锁只要求通道是可读的。这里的情况就是这样,因为我们从一个FileInputStream创建了它。

5.2. Shared Locks Using a RandomAccessFile

5.2.使用RandomAccessFile的共享锁

This time, we can open the file with just read permissions:

这一次,我们可以只用权限来打开文件。

try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "r"); 
     FileChannel channel = file.getChannel();
     FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) {
     // read from the channel
}

In this example, we created a RandomAccessFile with read permissions. We can create a readable channel from it and, thus, create a shared lock.

在这个例子中,我们创建了一个具有读取权限的RandomAccessFile。我们可以从它创建一个可读通道,从而创建一个共享锁。

5.3. Shared Locks Require a Readable FileChannel

5.3.共享锁需要一个可读的文件通道

For that reason, we can’t acquire a shared lock through a FileChannel created from a FileOutputStream:

出于这个原因,我们不能通过从FileOutputStream创建的FileChannel获得共享锁。

Path path = Files.createTempFile("foo","txt");
try (FileOutputStream fis = new FileOutputStream(path.toFile()); 
    FileLock lock = fis.getChannel().lock(0, Long.MAX_VALUE, true)) {
    // unreachable code
} catch (NonWritableChannelException e) { 
    // handle exception
}

In this example, the call to lock() tries to get a shared lock on a channel created from a FileOutputStream. Such a channel is write-only. It doesn’t fulfil the need that the channel has to be readable. This will trigger a NonWritableChannelException.

在这个例子中,对lock()的调用试图在一个由FileOutputStream创建的通道上获得一个共享锁。这样一个通道是只写的。它没有满足通道必须是可读的需求。这将触发一个NonWritableChannelException

Again, this snippet is just to demonstrate that we can’t read from a non-readable channel.

同样,这个片段只是为了证明我们不能从一个不可读的通道中读取。

6. Things to Consider

6.需要考虑的事项

In practice, using file locks is difficult; the locking mechanisms aren’t portable. We’ll need to craft our locking logic with this in mind.

在实践中,使用文件锁是很困难的;锁的机制是不可移植的。我们需要考虑到这一点来设计我们的锁定逻辑。

In POSIX systems, locks are advisory. Different processes reading or writing to a given file must agree on a locking protocol. This will ensure the file’s integrity. The OS itself won’t enforce any locking.

在POSIX系统中,锁是咨询性的。读取或写入一个给定文件的不同进程必须就锁定协议达成一致。这将确保文件的完整性。操作系统本身不会强制执行任何锁。

On Windows, locks will be exclusive unless sharing is allowed. Discussing the benefits or drawbacks of OS-specific mechanisms is outside the scope of this article. Yet, it’s important to know these nuances when implementing a locking mechanism.

在Windows上,除非允许共享,否则锁将是独占的。讨论操作系统特定机制的好处或缺点不在本文的讨论范围之内。然而,在实现锁定机制时,了解这些细微差别是很重要的。

7. Conclusion

7.结语

In this tutorial, we’ve reviewed several different options for obtaining file locks in Java.

在本教程中,我们回顾了在Java中获得文件锁的几种不同选择。

First, we started by understanding the two main locking mechanisms and how the Java NIO library facilitates locking files. Then we walked through a series of simple examples showing we can obtain exclusive and shared locks in our applications. We also took a look at the types of typical exceptions we might encounter when working with file locks.

首先,我们开始了解两种主要的锁机制,以及Java NIO库是如何促进锁文件的。然后,我们通过一系列简单的例子来说明我们可以在我们的应用程序中获得独占和共享锁。我们还看了一下我们在使用文件锁时可能遇到的典型异常类型。

As always, the source code for the examples is available over on GitHub.

像往常一样,这些例子的源代码可以在GitHub上找到