Java RMI和线程同步问题

6

我有两个关于Java RMI和线程同步的问题:

1)如果我将我的RMI远程方法实现为同步的,它们是否保证互斥?我需要确保我的两个RMI方法(提供给客户端的方法)不会同时执行。

2)我有一个服务器定期执行的方法。它用于清理。我必须确保当有任何远程客户端使用/运行RMI方法时,此特定方法不会执行。此外,在运行该方法时,不能进行RMI调用。即客户端必须等待。您有什么想法如何做到这一点?我已经阅读了锁的相关知识,但我不知道如何在这种情况下使用它们。

我考虑将RMI方法实现为静态方法,并在RMI接口中包含清理方法,但这似乎不是解决问题的优雅方式。

我还在RMI接口中编写了同步的清理方法。当我进行测试时,似乎没有发生方法之间的冲突,但我无法确定。

感谢您的时间和回答。


1
如果ewernli或我发布的任何内容对您有帮助,请至少点个赞吧;-) - stacker
4个回答

11

1) 如果我把我的RMI远程方法实现为同步的,它们是否保证互斥?我需要确保我的任何两个RMI方法(提供给客户端的方法)不会同时执行。

RMI本身不提供这种保证(不像EJB),除非您实现了一些同步机制,否则同一远程对象上的两个调用可能会同时执行。您的做法是正确的,同步所有方法确实可以确保它们不会在同一个对象上同时运行。注意:关键字synchronized单独使用等同于synchronized(this)

2) 我有一个服务器定期执行的方法。它用于进行清理。我必须确保在远程客户端正在运行/使用任何RMI方法时,此特定方法不执行。

如果清理作业在另一个类中,则需要定义一个锁,您将在远程对象和清理作业之间共享该锁。在远程对象中,定义一个实例变量,您将使用它作为锁。

protected Object lock = new Object();
按照惯例,人们使用Object来实现这个目的。然后在你的定期工作中使用synchronized(remoteObj.lock){...}获取锁,假设它在同一个包中。
远程对象中的其他方法也需要以相同的方式进行同步(仅使用synchronized是不够的),以使远程方法调用和定期工作都是互斥的。
我曾考虑过将RMI方法实现为静态方法,并将清理方法包含在RMI接口中,但这似乎不是解决问题的优雅方法。
我还将清理方法编写为在RMI接口中同步的。当我运行测试时,似乎没有发生方法之间的冲突,但我不能确定。
如果我理解正确,您希望清理逻辑成为静态方法?带有synchronized的静态方法会在类上抓取锁。带有synchronized的“常规”方法会在对象实例上抓取锁。这些不是相同的隐式锁!
但是,如果您只实例化了一个远程对象,则可以使lock是静态的(这与在类上锁定是相同的,但更加简洁)。清理代码也可以是静态的,并且与远程对象相同或不同类中。
代码框架:
public class MyRemoteClass {
   public static Object lock = new Object();

   public void doStuff()
   {
       synchronized( lock ) { ... }
   }
}

public class Cleanup {
   public static void doIt()
   {
       synchronized( MyRemoteClass.lock ) { ... }
   }
}

我不知道downvote是什么。无论如何,你能否详细解释一下锁定机制?我在另一个回复中看到了一个参考,但我并没有完全理解它。如果我在远程对象中实现锁定,那么如何从服务器本地对象获取锁定?类似于远程对象中的公共静态变量吗?或者,如果您可以提供任何链接/教程/代码片段,那也可以。谢谢。 :) - Inf.S
同步所有方法与我们所拥有的证据上的锁对象一样有效。 - user207421
@EJP 感谢您的解释。我已经编辑了我的答案。希望现在更好了。 - ewernli
@Inf.S:同步方法可以确保它们不会在同一个对象上同时运行。是否足够取决于您如何实现清理逻辑:是在单独的类中还是不是,作为静态方法还是不是。我建议您使用静态锁。 - ewernli
也可以。然后使用synchronized同步所有方法就足够了。 - ewernli
显示剩余4条评论

2
  1. 对于来自RMI客户端的每个调用,RMI服务器都将在新线程中执行调用。您只需要同步访问共享对象。

  2. 另一个线程或计时器不会阻止服务器接受来自客户端的调用。这需要同步,最佳实践取决于清理作业运行的时间长短是否可以中断,或者是否可以将请求放入队列等。 最简单的方法是让RMI方法等待锁,如ewernli所述。

编辑:根据您的评论,以下是演示如何实现此类基本同步的框架。由于现在所有内容都是互斥的,因此涉及多个客户端时不能期望高性能。无论如何,这将满足您的要求(我希望)。如果您的项目增长,您应该阅读并发教程

Object mutex = new Object();

int rmiMethod1() {
    synchronized (mutex) {
        doWhatNeeded1();
    }
}

int rmiMethod2() {
    synchronized (mutex) {
        doWhatNeeded2();
    }
}

// in your cleanup thread
void run() {
    synchronized (mutex) {
        cleanUp();
    }
}

我知道RMI会在新线程中执行新的调用。问题是我不想让两个远程方法同时运行,这包括不能多次调用同一个方法。我认为简单地使用同步方法可以解决这个问题。我通过在Google上搜索并阅读论坛找到了这个解决方案。那么,RMI是否肯定会忽略同步指令而继续并发运行远程方法呢? - Inf.S
非常感谢您提供这个很有说明性的例子。关于mutex对象。Synchronized(mutex):这行代码是干什么的?它是获取了所提供对象的锁吗?另外,为什么不像并发教程中所示,使用Lock对象呢?谢谢。 - Inf.S
synchronized(mutex)尝试获取所谓的监视器,以确保只有一个线程进入关键(受保护)部分。如果另一个线程已经进入关键部分,则所有其他线程都必须等待,直到它离开同步{}块。可以在教程中找到执行此操作的可能方法。这是我个人的首选。 - stacker
(1)不正确。首先,RMI的线程行为是故意和明确未指定的。其次,在Sun的实现中,连接池可以意味着来自同一客户端的连续调用在同一个线程中执行。所示代码相当于仅同步相关方法,这将更简单。 - user207421
@EJP 嗯,我多年后又读了一遍这篇文章。我可能试图描述我不时需要维护的系统中观察到的行为。 - stacker
如果来自任何一个客户端的调用间隔超过15秒,它们都将在新的服务器线程中执行。但是这些都没有明确指定,这是故意的。 - user207421

0

为了澄清所有的混淆:

  1. 如果您同步远程方法实现,那么一次只能有一个客户端在执行。

  2. 如果您将清理任务与远程对象同步,则它会加入(1)。

  3. 您不能将远程方法定义为静态。


-1

您必须记住RMI创建了一个“远程对象”的幻象,但实际上至少有三个对象:本地存根、远程骨架和实际的远程对象。作为设计折衷方案,这种幻象是不完全的,并且只锁定了本地存根。在网络上没有同步。搜索RMI+stub+synchronized,您会发现很多解释,例如:

Java RMI and synchronized methods

因此,您需要自己实现一些非RMI纯服务器端同步方式。然后,您可以从远程方法中调用这些纯服务器端锁;但您需要额外的间接层。

要测试您的代码,最简单的方法是在像Eclipse这样的良好调试器下暂停您的线程。Eclipse将清楚地显示哪个暂停的线程持有哪个锁,阻塞哪个其他线程。


自1998年以来,就没有骨架了。 - user207421
你是指“自Java 1998年以来”吗? 更严肃地说:是的,编译时骨架已被运行时等效物所取代。这与本页面有任何关联吗?我怀疑。 - MarcH
我的意思是我写的内容,如果有笑话我没理解到。如果这不相关,请将其从答案中删除。其余部分是好的。 - user207421
那“仅锁定本地存根”是什么意思? - user207421

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