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



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

请考虑以下向客户端提供资源字符串的服务器类。它永远不会提供两次相同的资源,因此它将提供的字符串存储在列表中。这是服务器引擎类:

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:");
        }
}
}

服务器引擎的实现方式会导致并发问题。如果在五秒内从两个不同的 VM 启动两个客户端,则它们都将返回相同的字符串。

从我到目前为止的研究来看,这是我解决问题的方法:

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((?

同步在几个级别上中断:

  • 你不应该wait()某件事,除非你期望其他线程notify()你。
  • 您只实现了一半双重检查锁定,这意味着"无锁定",因为相同的值可能会多次出现在列表中。
  • 您应该在 java.util.concurrent 下查看正确的线程安全集合实现,而不是手动执行此操作。

您的本地创建锁定对象是无用的,正如 tsolakp 所指出的,每个方法调用都会创建自己的实例。将对象创建为字段,以便将其监视器用于同步。

如果将方法声明为已同步,则隐式使用了调用该方法的实例的监视器。混合这两种方法是没有意义的。

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

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

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

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;
}

最新更新