我正在用 Java 制作一个客户端服务器应用程序。简而言之,服务器有一些文件。客户端可以向服务器发送文件,客户端可以请求从服务器下载所有文件。我使用 RMI 来让服务器和客户端进行通信,并且使用 RMI IO在客户端和服务器之间发送文件的库。
一些示例代码:
服务器:
class Server implements ServerService {
// private Map<String, File> files;
private ConcurrentHashMap<String, File> files // Solution
// adding a file to the server
public synchronized void addFile(RemoteInputStream inFile, String filename)
throws RemoteException, IOException {
// From RMI IO library
InputStream istream = RemoteInputStreamClient.wrap(inFile);
File f = new File(dir, filename);
FileOutputStream ostream = new FileOutputStream(f);
while (istream.available() > 0) {
ostream.write(istream.read());
}
istream.close();
ostream.close();
files.put(filename, f);
}
// requesting all files
public requestFiles(ClientService stub)
throws RemoteException, IOException {
for(File f: files.values()) {
//Open a stream to this file and give it to the Client
RemoteInputStreamServer istream = null;
istream = new SimpleRemoteInputStream(new BufferedInputStream(
new FileInputStream(f)));
stub.receiveFile(istream.export());
}
}
请注意,这只是一些用于演示的示例代码。
我的问题涉及对服务器上文件的并发访问。正如您所看到的,我已将 addFile
方法设置为synchronized
,因为它修改了我的服务器上的资源。我的 requestFiles
方法未同步
。
我想知道这是否会造成一些麻烦。当客户端A正在添加一个文件而客户端B同时请求所有文件时,或者反之亦然,这会造成麻烦吗?或者 addFile
方法会因为同步
而等待(或让其他方法等待)吗?
提前致谢!
最佳答案
是的,这可能会引起麻烦。其他线程可以访问 requestFiles(),而单个线程正在执行 addFile() 方法。
It is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.
[来源] http://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
因此,声明为同步的方法将实例锁定到该实例中的所有同步方法(在您的情况下是 Server 的实例)。如果您也同步了 requestFiles() 方法,那么您实际上将完全同步对服务器实例的访问。所以你不会有这个问题。
您还可以在文件映射上使用同步块(synchronized block)。请参阅这个 stackoverflow 问题: Java synchronized block vs. Collections.synchronizedMap
<小时/>话虽如此,每当写入或读取文件时本质上锁定整个服务器对象的模型会阻碍并发设计。
根据您的设计的其余部分,并假设您使用“addFile()”方法编写的每个文件都有不同的名称,并且您不会覆盖文件。我会探索如下内容:
完全删除映射,并让每个方法分别与文件系统交互。
我会对“addFile()”写入的文件使用临时 (.tmp) 扩展名,然后(一旦写入文件)执行原子文件重命名,将扩展名转换为“.txt”文件。
Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);
然后将整个“requestFiles()”方法限制为仅“.txt”文件。这样文件写入和文件读取可以并行发生。
显然使用您需要的任何扩展。
关于Java 并发读/写,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23310463/