多线程访问 Java 中的文件
Multithreaded access to files in Java
我正在 Java 的多线程服务器上工作。
服务器监视文件目录。客户端可以询问服务器:
- 从服务器目录下载文件
- 将现有文件的新版本上传到服务器,覆盖服务器目录中的旧版本。
为了进行传输,我计划使用 FileChannels 和 SocketChannels,使用方法 transferFrom 和 transferTo。根据文档,这两种方法是线程安全的。
问题是对这两个函数的一次调用不足以 read/write 整个文件。
如果同时对同一个文件有多个请求,就会出现问题。在这种情况下,多个线程可能对同一个文件执行 read/write 操作。现在,根据 Java 文档,对 transferFrom/transferTo 的单个调用是线程安全的。但是对这两个函数的一次调用不足以 read/write 整个文件。如果线程 A 正在回复下载请求,而线程 B 正在回复引用同一文件的上传请求,则可能会发生:
- 线程 A 开始读取文件
- 在线程 A 中,出于某种原因,在 EOF
之前读取调用 returns
- 线程 B 通过一次写入调用覆盖整个文件
- 线程 A 继续读取文件
此时下载客户端收到一部分旧版本和一部分新版本
为了解决这个问题,我想我应该使用某种锁定,但我不确定如何以有效的方式进行。我可以创建两个用于读取和写入的同步方法,但这显然会造成太多争用。
我想到的最佳解决方案是使用锁条带化。在执行任何 read/write 操作之前,会计算基于文件名的哈希值。然后,获取位置lockArr[hash % numOfLocks]的锁。
我还认为我应该使用 ReadWriteLocks,因为应该允许多个同时读取。
现在,这是我对问题的分析,我可能完全错了。对此有更好的解决方案吗?
锁定意味着有人必须等待其他人 -- 这不是最好的解决方案。
当客户端上传文件时,你应该把它写到同一磁盘上的一个临时文件中(通常在同一目录中),然后当文件上传完成时:
- 将旧版本重命名为临时名称。任何当前的读者都应该被迫关闭旧版本,重新打开临时版本,并寻找正确的位置。
- 将上传的文件重命名为目标文件名。
- 当任何读者使用完它时,删除旧文件的临时版本。
在典型的实现中,您需要一个集中式 class(我们称之为 ConcurrentFileAccessor
)来管理线程之间的交互。
读者需要注册此 class,并在实际读取操作期间在某些对象上同步。上传完成后,作者必须声明所有这些锁以阻止读取、关闭所有读取的文件、重命名旧版本、重新打开、查找,然后释放它们以允许读者继续。
我正在 Java 的多线程服务器上工作。 服务器监视文件目录。客户端可以询问服务器:
- 从服务器目录下载文件
- 将现有文件的新版本上传到服务器,覆盖服务器目录中的旧版本。
为了进行传输,我计划使用 FileChannels 和 SocketChannels,使用方法 transferFrom 和 transferTo。根据文档,这两种方法是线程安全的。 问题是对这两个函数的一次调用不足以 read/write 整个文件。
如果同时对同一个文件有多个请求,就会出现问题。在这种情况下,多个线程可能对同一个文件执行 read/write 操作。现在,根据 Java 文档,对 transferFrom/transferTo 的单个调用是线程安全的。但是对这两个函数的一次调用不足以 read/write 整个文件。如果线程 A 正在回复下载请求,而线程 B 正在回复引用同一文件的上传请求,则可能会发生:
- 线程 A 开始读取文件
- 在线程 A 中,出于某种原因,在 EOF 之前读取调用 returns
- 线程 B 通过一次写入调用覆盖整个文件
- 线程 A 继续读取文件
此时下载客户端收到一部分旧版本和一部分新版本
为了解决这个问题,我想我应该使用某种锁定,但我不确定如何以有效的方式进行。我可以创建两个用于读取和写入的同步方法,但这显然会造成太多争用。
我想到的最佳解决方案是使用锁条带化。在执行任何 read/write 操作之前,会计算基于文件名的哈希值。然后,获取位置lockArr[hash % numOfLocks]的锁。 我还认为我应该使用 ReadWriteLocks,因为应该允许多个同时读取。
现在,这是我对问题的分析,我可能完全错了。对此有更好的解决方案吗?
锁定意味着有人必须等待其他人 -- 这不是最好的解决方案。
当客户端上传文件时,你应该把它写到同一磁盘上的一个临时文件中(通常在同一目录中),然后当文件上传完成时:
- 将旧版本重命名为临时名称。任何当前的读者都应该被迫关闭旧版本,重新打开临时版本,并寻找正确的位置。
- 将上传的文件重命名为目标文件名。
- 当任何读者使用完它时,删除旧文件的临时版本。
在典型的实现中,您需要一个集中式 class(我们称之为 ConcurrentFileAccessor
)来管理线程之间的交互。
读者需要注册此 class,并在实际读取操作期间在某些对象上同步。上传完成后,作者必须声明所有这些锁以阻止读取、关闭所有读取的文件、重命名旧版本、重新打开、查找,然后释放它们以允许读者继续。