在单个JVM内部和跨多个JVM使用Java文件锁

5
我想我漏掉了一些东西,但是我不知道Java中的文件锁如何工作。更确切地说 - 它是如何实现的。
在单个JVM内似乎无法获取(甚至无法尝试获取)同一文件的两个或多个锁。第一个锁将被成功获取,在尝试获取更多锁时会导致OverlappingFileLockException异常。但对于单独的进程,则适用。
我想要实现由文件系统支持的数据存储,旨在处理多个并发请求(读取和写入)。我想使用文件锁定存储中特定文件上的锁。
看来我必须在JVM级别引入另一种同步(排它),然后再对文件进行同步以避免这个异常。
有人做过类似的事情吗?
我准备了一个简单的测试用例来展示我的问题。我使用的是Mac OS X,Java 6。
import junit.framework.*;

import javax.swing.*;
import java.io.*;
import java.nio.channels.*;

/**
 * Java file locks test.
 */
public class FileLocksTest extends TestCase {
    /** File path (on Windows file will be created under the root directory of the current drive). */
    private static final String LOCK_FILE_PATH = "/test-java-file-lock-tmp.bin";

    /**
     * @throws Exception If failed.
     */
    public void testWriteLocks() throws Exception {
        final File file = new File(LOCK_FILE_PATH);

        file.createNewFile();

        RandomAccessFile raf = new RandomAccessFile(file, "rw");

        System.out.println("Getting lock...");

        FileLock lock = raf.getChannel().lock();

        System.out.println("Obtained lock: " + lock);

        Thread thread = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    RandomAccessFile raf = new RandomAccessFile(file, "rw");

                    System.out.println("Getting lock (parallel thread)...");

                    FileLock lock = raf.getChannel().lock();

                    System.out.println("Obtained lock (parallel tread): " + lock);

                    lock.release();
                }
                catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        });

        thread.start();

        JOptionPane.showMessageDialog(null, "Press OK to release lock.");

        lock.release();

        thread.join();
    }

    /**
     * @throws Exception If failed.
     */
    public void testReadLocks() throws Exception {
        final File file = new File(LOCK_FILE_PATH);

        file.createNewFile();

        RandomAccessFile raf = new RandomAccessFile(file, "r");

        System.out.println("Getting lock...");

        FileLock lock = raf.getChannel().lock(0, Long.MAX_VALUE, true);

        System.out.println("Obtained lock: " + lock);

        Thread thread = new Thread(new Runnable() {
            @Override public void run() {
                try {
                    RandomAccessFile raf = new RandomAccessFile(file, "r");

                    System.out.println("Getting lock (parallel thread)...");

                    FileLock lock = raf.getChannel().lock(0, Long.MAX_VALUE, true);

                    System.out.println("Obtained lock (parallel thread): " + lock);

                    lock.release();

                }
                catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        });

        thread.start();

        JOptionPane.showMessageDialog(null, "Press OK to release lock.");

        lock.release();

        thread.join();
    }
}
3个回答

8

根据Javadoc:

文件锁是代表整个Java虚拟机持有的。它们不适用于在同一虚拟机内多个线程之间控制对文件的访问。


2

每个文件只能获取一次锁。据我所知,锁不可重入。

在进程之间使用文件进行通信是一个非常糟糕的想法。也许你能够可靠地使其工作,如果可以请告诉我 ;)

我会让一个线程在一个进程中进行读写操作。


1
这并不完全正确。可以为文件的特定区域获取锁定。因此,可以为单个文件获取多个锁定,只要每个锁定指定不同的、不重叠的区域即可。 - aroth
1
使用数据库,单个进程管理文件,您间接访问其文件。我发现尝试在多个主机上以高效和可靠的方式更新原始文件非常困难。 - Peter Lawrey
我同意,我应该说明我所考虑的不完全符合通信或者可能符合,我不是100%确定。我想把它称为进程间共享数据。但是进程正在读写这些数据,我应该称之为通信吗?无论如何,这就是我的用例。对于这种情况适合使用文件吗?FileLock给我留下的印象是可以将文件作为全局锁,或者说是进程间/进程内锁。就像我说的,我的用例只有这么多。那么,为什么要使用数据库或JMS呢?必须寻找IPC,文件绝对不行吗? - manocha_ak
同时也发现了关于File Steam构造函数的问题,它使用FileDescriptor作为参数时,无法覆盖一个文件,不知道我是否遗漏了什么? - manocha_ak
是的,我后来解决了。相对较新的nio是问题所在。我后来自己解决了。 - manocha_ak
显示剩余7条评论

2
您是否查看了文档FileChannel.lock()方法可以在整个文件上返回一个独占锁。如果您想要在不同的线程中同时使用多个锁,则不能使用此方法。
相反,您需要使用FileChannel.locklock(long position, long size, boolean shared)来锁定文件的特定区域。这将允许您同时拥有多个活动锁,前提是每个锁应用于文件的不同区域。如果尝试两次锁定文件的相同区域,则会遇到相同的异常。

我想要在整个文件上加锁(根据情况进行读或写),因为我在存储中有多个文件(每个文件都相当小)。 顺便说一句,aroth,你看到我的片段了吗?我似乎永远不会理解那个实现JDK锁的人的逻辑。为什么在多个进程中可以获取读锁,或者独占锁将阻塞线程,但是当我在同一个JVM中获取锁时会出现异常。当我调用lock()时,我希望当前线程阻塞直到获得锁。如果我不想阻塞,我可以使用tryLock。 - Yakov
@Yakov - 我明白你的意思,抛出异常而不是让调用者阻塞等待锁会让人感到困惑。但也许你可以通过类似 while ((lock = chan.tryLock()) == null) { Thread.sleep(100); } 的方式解决这个问题?或者你可以使用类似 synchronized(chan) { lock = chan.lock(); //... } 的模式来防止在同一个JVM中两次调用 lock() - aroth
tryLock 不总是有效 - 有时在尝试释放锁时,我会在 FileLockImpl 内部的某个地方收到 NPE(JDK 中未包含源代码)。因此,我认为这里唯一的解决方案是添加 JVM 级别的同步。谢谢。 - Yakov
还有一个问题。当需要文件锁的对象类使用不同的类加载器(例如,在Tomcat或类似应用程序中的不同应用程序中)加载时,JVM同步可能非常棘手。 - Yakov
真实但并没有解决真正的问题,即锁被进程而不是线程持有。 - user207421

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