Creating a simple program with RMI and parallel access to the graphical interface in Java

When I had to delve deeply into the use of RMI, I realized how important the ability to correctly implement parallelism plays in the graphical interface of the program. To my surprise, the Internet did not have enough material on this topic and especially qualitative examples, although the topic for any programmer is undoubtedly very important. That is why I decided to share my experience.

Briefly about RMI: the programming interface for calling remote methods in the Java language ( source ). Using it, for example, you can manage data on a server program from one or many computers. You can read moreon Habré. We will proceed from the fact that you are already familiar with its basics. You must also be aware of the innovations in Java 8, namely, you will need lambda expressions. There is a good explanation here .

The possibilities of using RMI are very diverse. Using it, you can make, for example, a chat or a voting program in Java. In my example, there will be a simple counter with a graphical shell, which looks like this:

image

  • JLable with current counter value
  • JButton "Plus" raises the counter value by one
  • JButton “Reset” resets the counter value to one


However, before moving on to the GUI, we create the RMI object itself, the counter, and the RMI server on which it will be stored.

Counter - Counter interface:

import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Counter extends Remote {
    final String NAME = "Counter";
    int reset() throws RemoteException;
    int increment() throws RemoteException;
}


Class initialization counter "CounterClass":

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class CounterClass extends UnicastRemoteObject implements Counter {
	private static final long serialVersionUID = 1L;
	private int counter;
    public CounterClass() throws RemoteException {
    }
    @Override
    public synchronized int reset() {
        this.counter = 0;
        return this.counter;
    }
    @Override
    public synchronized int increment() {
        this.counter++;
        return this.counter;
    }
}


Server for RMI counter "Counter_Server":

import java.io.IOException;
import java.rmi.AlreadyBoundException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Counter_Server {
    public static void main(final String[] args) throws IOException, AlreadyBoundException {
        CounterClass counter = new CounterClass();
        Registry localReg = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
        localReg.bind(Counter.NAME, counter);
        System.out.println("Counter-Server runs");
    }
}


Since I assume that you are already familiar with RMI, I will not explain these classes line by line. Brief explanation: the reset method equates the counter variable to 0 and returns it back, the increment method increments the counter variable by 1 and returns it back. In the server, create your register with the CounterClass skeleton. After that, the server can already be started.

Finally, move on to the chart. Let's create the class Counter_Client_GUI, which creates the frame itself with the GUI and at the same time takes the stub for the remote control of the counter from the previously created register through the main method:

import Counter.Counter;
public class Counter_Client_GUI extends JFrame {
	private static final long serialVersionUID = 1L;
	protected Counter counter;
        protected JLabel counterLabel;
    public Counter_Client_GUI(final Counter counter) {
    	this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        this.counter = counter;
        this.counterLabel = new JLabel("0", SwingConstants.CENTER);
        final JButton incrementButton = new JButton("Плюс");
        final JButton resetButton = new JButton("Сброс");
        incrementButton.addActionListener(this::incrementClicked);
        resetButton.addActionListener(this::resetClicked);
        this.setLayout(new GridLayout(0, 1));
        this.add(this.counterLabel);
        this.add(incrementButton);
        this.add(resetButton);
        this.setSize(300, 200);
        this.setVisible(true);
    }
    public static void main(String[] args) throws RemoteException, NotBoundException {
    	Registry reg = LocateRegistry.getRegistry("localhost");
    	Counter counter = (Counter) reg.lookup(Counter.NAME);
        new Counter_Client_GUI(counter);
    }


There are already some lines to explain:
  • incrementButton.addActionListener (this :: incrementClicked) - lambda expression, the Listener body is described in the incrementClicked method in the same class;
  • resetButton.addActionListener (this :: resetClicked) - lambda expression, the Listener body is described in the resetClicked method in the same class;
  • Registry reg = LocateRegistry.getRegistry ("localhost") - in this example, both the server and the client are on the same computer, so instead of a reference to the register, we specify "localhost".


Before the next step, you need to understand why a parallel approach is needed in this case. If we implement JLable updates in the most common way, for example:

this.counterLabel.setText(String.valueOf(novoeZnacheniePeremennoiCounter));


This is very likely to lead to a constant freezing of the frame, and at the same time it will not be possible to press a single button for a long time, although clicks are recorded and when the frame is thawed one by one, they will be executed, which will lead to chaos. This is due to the fact that in this case, all actions with the graphical shell will be taken by only one single Thread - EventDispatchThread . And do not forget that, in this example, the client and the server are on the same computer, the counter is still managed remotely , so there may be a failure in the RMI register or delayed delivery of the command to the server (in addition, this is only an example, but in real The client and server programs are of course not located on localhost).

Now let's get down to the most important part - we describe the incrementClicked and resetClicked methods , introducing the necessary parallelism :

    protected void incrementClicked(final ActionEvent ev) {
        new Thread(this::incrementOnGUI).start();
    }
    protected void resetClicked(final ActionEvent ev) {
        new Thread(this::resetOnGUI).start();
    }


Explanation: for every click on the button, create a new Thread and start it.

Inside each Thread is this:

    protected void incrementOnGUI() {
        try {
            final int doAndGetIncrement= this.counter.increment();
            final String newLabelText = String.valueOf(doAndGetIncrement);
            EventQueue.invokeLater(() -> this.counterLabel.setText(newLabelText));
        } catch (final RemoteException re) {
            final String message = "Fehler: " + re.getMessage();
            EventQueue.invokeLater(() -> JOptionPane.showMessageDialog(this, message));
        }
    }
    protected void resetOnGUI() {
        try {
            final int doAndGetReset= this.counter.reset();
            final String newLabelText = String.valueOf(doAndGetReset);
            EventQueue.invokeLater(() -> this.counterLabel.setText(newLabelText));
        } catch (final RemoteException re) {
            final String message = "Fehler: " + re.getMessage();
            EventQueue.invokeLater(() -> JOptionPane.showMessageDialog(this, message));
        }
    }


EventQueue.invokeLater (...) is the key point of the program. EventQueue from English "event queue" is a function that (contained in Java) sends the task of the current Thread to the queue for execution in the main Thread. In our case, the task is updating the counter this.counterLabel.setText(newLabelText)or displaying an error message JOptionPane.showMessageDialog(this, message). This is necessary to avoid confusion among the work of many created Threads. For example, a method will count the number of rows in one Thread in the table, and another Thread will delete the rows. With a high probability, the resulting number will be incorrect. Eventually, EventQueue contains a list of tasks that are performed in turn or by availability, without interfering with any other work with the graphical interface.

It is worth noting that in the body of EventQueue.invokeLater (...) there is also a lambda expression. In this program, they are used for a better and more understandable appearance of the code.

That's all. Now parallelism with the graphical shell of the program is provided in any case, even when working with RMI.

Thanks for attention!

Also popular now: