Search code examples
javamultithreadingthread-safetyrmi

Is RMI automatically "thread safe"?


I'm learning how to use Java RMI in order to code distribuded applications. I wrote a simple program just to test some things out, I just give integers to a server through a client, and the server accumulates them in a static variable. Here is the code:

The server:

public class Adder extends UnicastRemoteObject implements IAdder {

/**
 * 
 */
private static final long serialVersionUID = 8229278619948724254L;
private static Integer sum = 0; 
static Logger logger = Logger.getLogger("global");

protected Adder() throws RemoteException {
    super();
    // TODO Auto-generated constructor stub
}

@Override
public void add(int i) throws RemoteException {
    System.out.println("Got: " + i);
    synchronized (sum) {
        sum += i;
        System.out.println("The new sum is: " + sum);
    }
}

@Override
public int result() throws RemoteException {
    System.out.println("The sum is: " + sum);
    return sum;
}


public static void main(String[] args) {
    System.setSecurityManager(new SecurityManager());
    try  {
        logger.info("Building remote object");
        Adder obj = new Adder();
        logger.info("Binding");
        Naming.rebind("SommaServer", obj);
        logger.info("Ready");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}

Client:

public class Client extends Thread {

static Logger logger = Logger.getLogger("global");
private IAdder obj;
private int array[] = new int[3];

public static void main(String[] args) {
    try {
        Client c1 = new Client(1);
        Client c2 = new Client(2);
        c1.start();
        c2.start();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void work(int i) throws RemoteException {
    obj.add(i);
}

public void run()  {
    Random rn = new Random();
    try {
        work(array[0]);
        work(array[1]);
        work(array[2]);
    } catch (Exception e) {
        e.printStackTrace();
    }   
}

public Client(int i) throws Exception {
    if (i == 1) {
        array[0] = 1;
        array[1] = 3;
        array[2] = 4;
    }
    if (i == 2) {
        array[0] = 7;
        array[1] = 2;
        array[2] = 5;
    }
    logger.info("Remote object lookup");
    obj = (IAdder) Naming.lookup("rmi://localhost/SommaServer");

}

}

In the main of the client I create two client threads, and run them (there are no synchronicity checks I know, but I'm just trying things out). Each thread has an array of numbers to feed to the server. The server receives them and then adds them all togheter.

So, since I'm dealing with threads, my first thought was that I needed to use a lock on the update of the sum, otherwise I would likely get errors because of the interleaving of the threads. Hence the synchronized block in the server.

But then, to see what would happen, I removed the block, and I was still getting the right result, all the time (the values is 22).

Just to be sure I made a "local" version of the client too, which updates a local variable:

public class Client extends Thread {
private static Integer sum = 0;
static Logger logger = Logger.getLogger("global");
private IAdder obj;
private int array[] = new int[3];

public static void main(String[] args) {
    try {
        Client c1 = new Client(1);
        Client c2 = new Client(2);
        c1.start();
        c2.start();
        c1.join();
        c2.join();
        System.out.println("Ricevuto: " + sum);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void work(int i) throws RemoteException {
    //obj.add(i);
    synchronized(sum) {
        sum += i;
    }
}

public void run()  {
    Random rn = new Random();
    try {
        work(array[0]);
        work(array[1]);
        work(array[2]);
    } catch (Exception e) {
        e.printStackTrace();
    }   
}

public Client(int i) throws Exception {
    if (i == 1) {
        array[0] = 1;
        array[1] = 3;
        array[2] = 4;
    }
    if (i == 2) {
        array[0] = 7;
        array[1] = 2;
        array[2] = 5;
    }
}

}

With synchronization I get the right result, without I get various numbers (8, 15, 14, 22...)

So, what is going on exactly? I doubt RMI can just be thread safe like that.

Extra question: When I bind an object in RMI, what exactly am I binding? The specific instance of the object I call Naming.rebind() on, or the class(and then when I look it up I just get a new instance?)?


Solution

  • Your synchronization is broken, so you can't deduce much by your experiment. Each time you do sum += i, you assign a new Integer to sum. So the threads synchronize on two different Integers.

    Also, you're binding an instance of Sommatore, but you're showing the code of Adder.

    But to answer your question, you're binding an instance, and RMI won't magically create copies of that instance. It won't synchronize all calls to this instance either. So you need to make sure the code is thread-safe. Just because a quick test produces a correct result despite the absence of proper synchronization doesn't mean that it will always be that way. You can cross a road with your eyes shut 10 times and never die. That doesn't mean it's safe.