Java RMI和同步方法

16
我正在学习《分布式系统》一书(作者为 Tanenbaum 和 Van Steen),他们说的内容似乎与许多关于 Java RMI 和同步方法的看法相冲突。
我认为,在远程对象实现上使用同步方法(因此在服务器上运行的真正实现)即使调用该方法的客户端机器不同(通过代理调用该方法...也就是存根),也会防止对该方法的并发执行。
我发现很多人有同样的观点,例如在这里:Java RMI 和线程同步问题 但在书中却说,使用RMI时,同步方法的并发执行并未被阻止。
以下是书中相关摘录(您可以只阅读粗体句子,但如果您愿意,也可以阅读上下文):
逻辑上,在远程对象中进行阻塞是很简单的。假设客户端A调用了远程对象的同步方法。为了使对远程对象的访问看起来始终与对本地对象的访问完全相同,需要在实现对象接口的客户端存根中阻止A,并且A可以直接访问该存根。同样,在将请求发送到服务器之前,不同机器上的另一个客户端也需要在本地被阻塞。因此,我们需要在不同的机器上同步不同的客户端。正如我们在第6章中讨论的那样,分布式同步可能会相当复杂。 另一种方法是仅允许在服务器上进行阻塞。原则上,这样做没问题,但是当客户端在服务器处理其调用时崩溃时会出现问题。正如我们在第8章中讨论的那样,我们可能需要相对复杂的协议来处理这种情况,这可能会显着影响远程方法调用的总体性能。 因此,Java RMI的设计者选择仅限制对代理的远程对象进行阻塞(Wollrath等人,1996年)。这意味着在同一进程中的线程将无法同时访问相同的远程对象,但在不同进程中的线程则不会。显然,这些同步语义很棘手:在句法层面(即阅读源代码时)我们可能会看到一个漂亮、干净的设计。只有在实际执行分布式应用程序时,才会观察到可能应该在设计时处理的未预期行为。[...]
我认为文中的注释Wollrath et all,1996是指向论文"A Distributed Object Model for the Java System"(可在此处获得)。然而,我在那篇论文中找到的唯一相关段落是这个:

由于本地对象和远程对象存在不同的故障模式,分布式等待和通知需要在涉及实体之间使用更复杂的协议(例如,客户端崩溃不会导致远程对象永久锁定),因此不能轻松地适应Java中的本地线程模型。因此,客户端可以在远程引用上使用notify和wait方法,但该客户端必须意识到这样的操作不涉及实际的远程对象,只涉及远程对象的本地代理(存根)。

我是错误地解释了文本,还是实际上陈述了当使用RMI时同步方法“不太同步”?

4个回答

12
你第一份参考资料所说的是,在单个VM实例中,对RMI存根(客户端到RMI服务器)的调用将被内部同步化。也就是说,存根本身会防止多个线程同时调用远程服务器上的方法。然而,它澄清了两个拥有远程服务器存根的VM将不会被阻止同时调用远程服务器(这是显而易见的,因为它们不能共享锁,并且RMI本身不会防止服务器并发)。如果这是不可取的,RMI服务器将必须实现一个锁定机制来防止多个并发调用。
第二个参考并不以任何方式与第一个相矛盾。第二个只是澄清,如果你尝试在存根上进行同步,它将只在本地被锁定,不会影响远程服务器的并发性。
结合这两个文本,我们可以看出,在存根上同步将防止同一VM中的多个线程同时访问远程,但不会防止不同VM中的线程并发访问。

非常好的分析和解释。但是必须注意到,引用文本中声称RMI存根在内部针对并发本地调用进行同步的说法是完全错误的。 - user207421
实际上,我认为是我的分析有误,而不是引用文本中的声明有误。在仔细重新阅读引用文本后,似乎作者的意图是传达类似于“在远程对象同步的情况下,阻塞仅限于代理”的内容。在这种情况下,我的原始文本分析是错误的,因为没有关于内部同步的声明。 - mwhidden
作者的声明是不正确的,并且可以通过实验轻松证明。事实上,引用的文本相当荒谬,而且也无法实现。请看我的回答。 - user207421

1

你说得对。原文是错误的。RMI存根是线程安全的,可以在单个客户端JVM内同时被多个线程调用。我不知道Wollrath等人有任何不同的声明或文本,自1997年以来我一直在关注这个话题。

具体而言:

我认为,在Remote Object实现上使用同步方法(因此在运行于服务器上的实际实现中)即使从不同的客户端机器(通过代理调用该方法...即一个Stub)调用该方法也会防止该方法的并发执行。

你是对的。

书上说使用RMI时同步方法的并发执行不能被阻止。

这本书不仅是错的,它还陈述了一个不可能的情况。RMI怎么可能防止同步工作呢?

逻辑上,在远程对象中进行阻塞非常简单。假设客户端A调用远程对象的同步方法。

然后,由Java的正常操作,在服务器上会发生阻塞。

为了使远程对象的访问看起来与本地对象完全相同,需要在实现对象接口并且直接访问A的客户端存根中阻止A。
胡说八道。远程方法实现是同步的,这已经做到了必要的一切。
同样,另一个位于不同机器上的客户端在发送请求到服务器之前也需要在本地被阻止。
这又是胡说八道。
结果是我们需要在不同的机器上同步不同的客户端。
另一种方法是只允许在服务器上进行阻塞。
“允许”?这是什么意思?同步方法是同步的。你不能“禁止”它。
原则上,这很好用,但当客户端在服务器处理其调用时崩溃时会出现问题。

再说了,这些都是废话。没有这样的问题出现。服务器通过读取超时、写入异常或远程方法的成功完成来恢复这种情况。在这三种情况下,方法退出,同步锁被释放,生活继续。

正如我们在第8章中讨论的那样,我们可能需要相对复杂的协议来处理这种情况,这可能会显著影响远程方法调用的整体性能。

胡说八道。

因此,Java RMI的设计者选择仅将远程对象上的阻塞限制在代理上(Wollrath等人,1996年)。

我不知道这还能指代其他什么,除了你引用的那篇论文,而且我已经多次阅读过那篇论文。如果作者想要依赖这篇论文,他们应该提供一个引语和一个适当的引用章节和节。

无论如何,RMI的设计者都没有做出这样的选择。根本就没有这样的选择。无论RMI的设计者是否希望,synchronized都是synchronized,同样地,notify()wait()也是final的。他们不能自由地做出任何选择。你提供的引用不是一个“选择”,而只是关于Java语义的陈述。
“我是否错误地解释了文本,或者实际上声明了当使用RMI时同步方法是‘不那么同步’?”
我认为你理解得没错,但这完全是错误的,而且显然是错误的。它怎么可能是正确的呢?Java RMI不能以任何方式改变、删除或扩展synchronized的语义。

1
这些错误相当明显,这段文本怎么能通过审核呢?! - Bilal Siddiqui

1
据我所知,每次调用RMI服务器都会在服务器端创建一个新线程(根据我从2000年的日志文件中观察到的情况)。如果您在服务器端进行同步,那么应该是安全的。我遇到了一些来自文献的古老警告,就像您发布的那样。作为一名实践者,我更喜欢运行软件一个月左右,并决定它已经足够稳定以供生产使用。如果这不能令您满意,我很抱歉。

不完全正确。有连接池,因此来自同一JVM的连续调用可能会使用相同的连接,从而在服务器上使用相同的线程。 - user207421

1
你还应该知道,自1996年以来,Java多线程已经发生了重大变化。原始语言设计中的notify()和wait()方法受到并发专家的批评,在Java 5(2004年,维基百科上说)引入了ReentrantLock高级并发对象,现在这是做事情的首选方式。
所以,你提到的批评可能是正确的,但已经过时了。

这些更改对RMI或此问题提出的事项没有任何影响。 - user207421

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