几天前我开始使用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;
}