如何最有效地从多个线程访问共享文件?

How to access a shared file from multiple threads most effectively?

我正在开发一个小型网络应用程序,其 servlet 会定期访问共享资源,该共享资源是服务器端的一个简单文本文件,其中包含一些可变数据行。大多数时候,servelts 只是读取文件中的数据,但有些 servelts 也可能会更新它,向文件添加新行或删除和替换现有行。尽管文件内容不经常更新,但如果两个或多个 servlet 决定同时读取和写入文件,则数据不一致和文件损坏的可能性仍然很小。

第一个目标是使文件 reading/writing 安全。为此,我创建了一个帮助程序 FileReaderWriter class,它提供了一些用于线程安全文件访问的静态方法。读写方式由ReentrantReadWiteLock协调。规则很简单:只要没有其他线程同时写入文件,多个线程可以随时从文件中读取。

public class FileReaderWriter {
    private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public static List<String> read(Path path) {
        List<String> list = new ArrayList<>();
        rwLock.readLock().lock();
        try {
            list = Files.readAllLines(path);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
        return list;
    }

    public static void write(Path path, List<String> list) {
        rwLock.writeLock().lock();
        try {
            Files.write(path, list);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

那么,每个servelt都可以像这样使用上面的方法读取文件:

String dataDir = getServletContext().getInitParameter("data-directory");
Path filePath = Paths.get(dataDir, "test.txt");
ArrayList<String> list  = FileReaderWriter.read(filePath);

同理,也可以用FileReaderWriter.write(filePath, list)方式写。注意:如果需要替换或删除某些数据(这意味着从文件中获取数据,对其进行处理并将更新后的数据写回文件),则此操作的整个代码路径应由 rwLock.writeLock() 锁定出于原子性原因。

现在,当访问共享文件似乎是安全的(至少,我希望如此)时,下一步就是让它变得更快。从可伸缩性的角度来看,在每个用户向 servlet 发出请求时读取一个文件听起来并不合理。所以,我想到的是在上下文初始化期间仅将文件内容读入 ArrayList (或其他集合)一次,然后将此 ArrayList (不是文件)共享为上下文范围的数据持有者属性.然后 servlet 可以使用与上述相同的锁定机制共享一个上下文范围的属性,并且可以定期将更新的 ArrayList 的内容独立地存储回文件。

另一种解决方案(为了避免锁定)是使用 CopyOnWriteArrayList(或 java.util.concurrent 包中的其他集合)来保存共享数据并指定单线程 ExecutorService 在需要时将其内容转储到文件中。我还听说过 Java Memory-Mapped Files 用于将整个文件映射到内部存储器,但不确定这种方法是否适合这种特定情况。

所以,如果写入文件的频率很低且内容很少,请有人指导我解决共享文件访问问题的最有效方法(也许,建议其他替代方法)预计不会超过几十行。

你没有说明你真正的问题,仅凭你目前的尝试,很难提供好的解决方案。

你的方法有两个严重的问题:

问题一:并发

a shared resource which is a simple text-file on the server side holding some lines of mutable data

解决一个问题的90%都是好的数据结构。它不是一个 mutable 文件。即使流行的数据库引擎也有重要的并发限制(例如SQLite),不要试图重新发明轮子。

问题2:水平可扩展性

即使他解决了本地并发问题(例如同步方法),您也无法部署应用程序的多个实例 (nodes/servers)。

解决方案 1:使用正确的工具完成工作

您没有准确解释您的(数据管理)问题的性质,但可能任何 NoSQL database will do you good (reading about MongoDB 都可以作为一个很好的起点。

(坏)方案二:使用FileLock

如果出于某种原因您坚持按照您的指示进行操作,请使用 FileLock 使用低级文件锁。您将只需要处理部分文件锁,甚至这些都可以水平分布。您也不必担心同步其他资源,因为文件级锁就足够了。

(有限)方案三:在内存结构中

如果您不需要水平可伸缩性,您可以使用像 ConcurrentHashMap 这样的内存共享结构,但是如果您不在应用程序之前保留信息,您将失去水平可伸缩性并且可能会丢失事务停止。

结论

虽然有更多奇特的分布式数据模型,但即使是单个 table 使用数据库可能是最好和最简单的解决方案。