RMI:多个客户端访问服务器时的正确同步

时间:2017-11-10 22:17:24

标签: java rmi

几天前我开始使用Java RMI。我想知道以下示例是否正确同步。

考虑以下为客户端提供资源字符串的Server类。它永远不会提供两次相同的资源,因此它将提供的字符串存储在列表中。这是ServerEngine类:

package dummy;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;

public class ServerEngine implements Server {
    private final String s1 = "Resource Object 1";
    private final String s2 = "Resource Object 2";
    private final LinkedList<String> list = new LinkedList<>();
    private final int timer = 5000;

    public static void main(String[] args) {
        try {
            String name = "server";
            ServerEngine engine = new ServerEngine();
            Server stub = (Server) UnicastRemoteObject.exportObject(engine, 0);
            Registry registry = LocateRegistry.getRegistry();
            registry.rebind(name, stub);
            System.out.println("ServerEngine bound");
        } catch (Exception e) {
            System.err.println("ServerEngine exception:");
        }
    }

    @Override
    public String getResource() throws RemoteException {
        Object lock = new Object();

        if ( ! list.contains(s1)) {
            synchronized (lock) {
                // wait to ensure concurrency
                try {
                    lock.wait(timer);
                } catch (InterruptedException ex) {}
            }
            list.add(s1);
            return s1;
        }

        if ( ! list.contains(s2)) {
            list.add(s2);
            return s2;
        }

        return null;
    }
}

服务器界面:

package dummy;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Server extends Remote {
    public String getResource(boolean synced) throws RemoteException;
}

和客户:

package dummy;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
public static void main(String[] args) {
        try {
            String name = "server";
            Registry registry = LocateRegistry.getRegistry();
            Server server = (Server) registry.lookup(name);

            boolean sync = args.length > 0;
            String s = server.getResource(sync);
            System.out.println("Resource: " + s);
        } catch (Exception e) {
            System.err.println("Client exception:");
        }
}

}

ServerEngine的实现方式会导致并发问题。如果两个客户端在五秒钟内从两个不同的VM启动,那么它们将返回相同的String。

根据我目前的研究,这是我解决问题的方法:

package dummy;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;

public class ServerEngine implements Server {
    private final String s1 = "Resource Object 1";
    private final String s2 = "Resource Object 2";
    private final LinkedList<String> list = new LinkedList<>();
    private final int timer = 5000;

    public static void main(String[] args) {
        try {
            String name = "server";
            ServerEngine engine = new ServerEngine();
            Server stub = (Server) UnicastRemoteObject.exportObject(engine, 0);
            Registry registry = LocateRegistry.getRegistry();
            registry.rebind(name, stub);
            System.out.println("ServerEngine bound");
        } catch (Exception e) {
            System.err.println("ServerEngine exception:");
        }
    }

    private synchronized String localGetResource() {
        Object lock = new Object();

        if ( ! list.contains(s1)) {
            synchronized (lock) {
                // wait to ensure concurrency
                try {
                    lock.wait(timer);
                } catch (InterruptedException ex) {}
            }
            list.add(s1);
            return s1;
        }

        if ( ! list.contains(s2)) {
            list.add(s2);
            return s2;
        }

        return null;

    }

    @Override
    public String getResource() throws RemoteException {
        return localGetResource();
    }
}

我想知道这是否是一个可行的解决方案。有什么警告吗?我真的需要第二个函数,还是可以直接同步getResource()?

3 个答案:

答案 0 :(得分:2)

您的同步在几个级别上被破坏:

  • 你不应该对某事做wait(),除非你期望有其他线索到notify()你。
  • 您只实现double-checked locking的一半,转换为“无锁定”,因为相同的值可能会多次在列表中结束。
  • 您应该查看java.util.concurrent下正确的线程安全集合实现,而不是手动执行此操作。

答案 1 :(得分:0)

你的本地创建锁对象是无用的,正如tsolakp所说,每个方法调用都会创建自己的实例。 将对象创建为字段,以便使用它的监视器进行同步。

如果将方法声明为synchronized,则会隐式使用调用该方法的实例的监视器。混合这两种方法毫无意义。

如果要同步对列表的访问,请使用相应对象的监视器进行同步。

答案 2 :(得分:0)

  

我想知道以下示例是否正确同步。

根本没有同步。它使用锁定,但不正确,因此它也不是顺序化

public String getResource() throws RemoteException {
    Object lock = new Object();

    if ( ! list.contains(s1)) {
        synchronized (lock) {
            // wait to ensure concurrency
            try {
                lock.wait(timer);
            } catch (InterruptedException ex) {}
        }
        list.add(s1);
        return s1;
    }

    if ( ! list.contains(s2)) {
        list.add(s2);
        return s2;
    }

    return null;
}

你不需要这一切,你当然不需要wait()。此代码实际上永远不会有效地锁定列表,因为每次调用都会获得自己的锁定对象。

把它扔掉,然后同步方法:

public synchronized String getResource() throws RemoteException {

    if ( ! list.contains(s1)) {
        list.add(s1);
        return s1;
    }

    if ( ! list.contains(s2)) {
        list.add(s2);
        return s2;
    }
    return null;
}