【问题标题】:Is using FileLock necessary to avoid issues with Reading & Writing or Writing & Writing at the same time?是否有必要使用 FileLock 来避免同时出现读写或写作和写作问题?
【发布时间】:2015-01-01 19:25:03
【问题描述】:

我正在设计一个应用程序,它的主线程通过读取和写入来与 .txt 文件交互。

这个应用还有一个后台 IntentService 可以读写这个文件。

我需要能够读入一个文件,并用新数据将其写回,同时确保另一个线程在这两个步骤之间没有更改文件的数据。

我认为 FileLock 是我想要的,但是当我开始读取文件时,我无法锁定文件,因为 InputFileStream.getChannel().lock() 给出了异常。 (NonWritableChannelException)。

我希望代码类似于:

File theFile = new File(file.txt);
FileInputStream in = new FileInputStream(theFile);

//should block here if it can't immediately lock
FileLock fileLock = in.getChannel().lock(); 

String inString = "";

int content;
while((content = in.read()) != -1){
    inString += Character.toString(((char) content));
 }
in.close();

inString += "new data";

out = new FileOutputStream(file);

out.write(finalString.getBytes());

 out.flush();
 out.close();

//releases the lock
fileLock.release();

我意识到可能可行的一个场景是使用 RandomAccessFile。因为在读取这些内容时,您可以声明它们“rw”权限(读写),因此您可以使用 .getChannel().lock() 但我想在深入研究之前先询问社区。​​p>

【问题讨论】:

  • “我正在设计一个应用程序,它的主线程通过读取和写入来与 .txt 文件进行交互。这个应用程序还有一个后台 IntentService 可以读取和写入这个文件。” - 为什么?为什么不使用一个具有工作队列的实体来负责写入和读取或返回缓存数据?另外,我希望当您提到“主线程”执行读取时,您并不意味着您正在主应用程序线程上执行磁盘 I/O,因为这会导致卡顿。
  • 这些是小文件(几百个字符)。为什么有后台线程访问文件?因为它是一个使用 AlarmManager 在预定时间在手机后台执行操作的应用程序。它需要做互联网下载,不能在A广播接收器类中运行,因此广播接收器类必须运行一个意图服务,其目的是在线检查文件。如果某个条件发生了,则需要写入用户界面读取的文件,以便将信息显示给用户。
  • 这篇文章的名字很糟糕。一个更好的标题应该是:如何在确保没有其他线程可以修改文件的同时修改文件?
  • “如果发生某种情况,它需要写入用户界面读取的文件,以便向用户显示信息” - 这就是我的建议可能是重新设计。特别是对于小文件,请使用由文件支持的内存缓存。您的服务会更新文件并使用进程内事件总线让 UI 层知道(如果当前存在)数据已更改。唯一需要读取文件的时间是重新启动进程时,如果您在执行写入的同一 IntentService 中执行此操作,则 I/O 会自动序列化。
  • 你是对的!好的,所以我有这个文件来跟踪正在运行的警报管理器,并且这个文件还有线程将写入它的“更新”数据。创建新的 AlarmManager 时,UI 必须更改此文件。相反,我会将这些变化的数据移动到一个单独的文件中,该接口将只读取,而不再需要写入。

标签: android file synchronization locking


【解决方案1】:

一般来说,对于单进程应用程序(大多数 Android 应用程序都是),线程同步是要走的路线,比文件系统级锁定更重要。

除此之外,一般来说,您希望尽量减少磁盘 I/O,因为这很慢。

特别是对于小文件,请使用由文件支持的内存缓存。您的服务会更新文件并使用进程内事件总线(LocalBroadcastManager、greenrobot 的 EventBus、Square 的 Otto)让 UI 层知道(如果当前存在)数据已更改。 UI 层专注于为数据使用内存中的缓存。

您需要读取文件的唯一时间是您的进程重新启动时,如果您在执行写入操作的IntentService 中执行此操作,则 I/O 会自动序列化。如果您的 UI 在启动时检测到进程内缓存为空,它只会发送一个命令来启动服务以读取该缓存。再一次,该服务使用事件总线让 UI 层知道数据现在可以使用了。

这样,您将磁盘读取减少到所需的最低限度,并且同步磁盘 I/O(IntentService 仅使用一个线程,并且如果多个命令同时进入,则有一个工作队列),所以您不必担心应用程序的一部分会影响应用程序的其余部分。

【讨论】:

  • 我从未使用过缓存——一旦我的原型开始工作,我就必须尝试一下。目前,我的 UI 实例化了一个 FileObserver,并将其传递给在 Looper.getMainLooper() 上运行的处理程序。当文件更改时,文件观察器实例将消息传递给处理程序。当处理程序的 handleMessage 触发时,UI 会更新。这暂时有效。谢谢!
  • 我对您的回答有了新的启示。除了所有东西都(某种程度)存储在一个地方(硬盘)这一事实之外,我们还有不同文件的抽象。他们这样做是为了让我们可以在我们的程序中分离我们的“对话”。从两个不同的线程写入和读取同一个文件就像在别人说话时试图说话。只需在不同的频道上发言! AKA 写入不同的文件,并从不同的文件中读取。您从 A 读取并写入 B。他们从 B 读取并写入 A!
  • 最终用互斥锁和 SQLite 解决了这个问题
【解决方案2】:

使用随机存取文件可能会导致性能下降。

根据此链接 - http://docs.oracle.com/javase/6/docs/api/java/io/RandomAccessFile.html#RandomAccessFile%28java.io.File,%20java.lang.String%29

"rws"   Open for reading and writing, as with "rw", and also require that every update to the file's content or metadata be written synchronously to the underlying storage device.
"rwd"   Open for reading and writing, as with "rw", and also require that every update to the file's content be written synchronously to the underlying storage device. 

如果将文件内容写入设备,将比其他方法慢得多。

就您的 NonWritableChannelException 而言,它的发生是因为您试图锁定输入流。如果要加锁,需要打开文件进行写操作。

希望这会有所帮助。

【讨论】:

  • 感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-26
  • 2011-05-09
  • 2012-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多