Přeskočit na hlavní obsah

Really responsive Java application

This article isn’t about concurrency or threads synchronizing, neither about responsive design. It is about making your Java application user responsible and friendly.

All that threads


Working with threads in Java is … well I can’t say easy, but at least well documented and great supported.
You can create your own Thread object and pass it the Runnable object with logic, which should be executed in background. You can use SwingWorker API, which is great for long running tasks with callbacks to GUI (for example to update progressbar). There is also ExecutorService (part of java.util.cuncurent API), which solves asynchronous issues and starting in Java 7 there is the Fork/Join framework to add even more support for parallel programming (but it solves different problem, than described in this article).

Working with threads has its own quirks, but when using threads together with GUI, we always have to keep in mind two major rules:

  • Time–consuming tasks shouldn’t be run on Event Dispatch Thread (just a few seconds running task should be considered and treated as time-consuming)
  •  Swing components should ALWAYS be accessed from EDT (no exceptions).
More on this can be found here: javadoc

It seems, that everybody knows these rules, but you can still find in many Java applications code, that computes ultimate question of life, the universe and everything or calls some web service directly from the Event Dispatch Thread. Why is that done?

Well, because it is easy

Calling some long-running computation or service in EDT is so tempting because it works :-), it’s easy :-| and it block’s the Event Dispatch Thread :-( And the blocking of EDT is the reason why you can find it literally everywhere – I click the “Compute” button, start the computation and I want to wait for the result, before I do something else (fill the table with results for example). This works simply because I am using one thread for everything and this thread is busy doing my long-running task. But there are drawbacks…

Drawbacks

Drawbacks of using EDT to run long tasks don’t have to be always seen, until your tasks are really long. How long? It depends. There was a rule, that any action longer than 15 seconds should notify user of its results, via some progressbar, or just say that something is happening. But Moore’s law is still valid and computers are faster every year and users are more and more demanding and the psychological barrier of 15 seconds was long time ago pushed to one or two seconds.
Users just don’t want to wait, and if so, they want to know what is happening and when it will end, or even more they want to have choice to cancel the operation.

So the user is waiting, what is so bad about it?

The bad thing is that the application doesn’t respond. Because the EDT is doing your computation, or waiting for some response, it can’t process input events (mouse, keyboard) or even paint. Your progressbar doesn’t move, it maybe doesn’t event paint. And even worse – the same happen to all application frame. You can see it, when you run such application, start some long task, minimize it and try to restore it back. All you see now is only black rectangle. This is starting to be bad, doesn’t?

Example of application, running computation on EDT:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;

public class BusyApp {

 public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {
   
   public void run() {
    JFrame frame = new JFrame();
    JPanel panel = new JPanel();

    JProgressBar progressBar = new JProgressBar();
    progressBar.setIndeterminate(true);
    panel.add(progressBar);

    frame.setTitle("Busy application demo");
    frame.setContentPane(panel);
    frame.setSize(300, 80);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
    
    System.out.println("Start of execution");
    long result = 0;
    for (long i = 0; i < 100000000l; i++) {
     Float f = (float) (Math.sin(i) * i); //doing some computation to slow it down
     result = i + result;
    }
    System.out.println("End of execution");
   }
   
  });
 }
 
}

Is there a better way?

There is always some better way, but there is also its price. You have to change your thinking and not just block your main thread, but create background action and use a callback to invoke your reaction (filling the table with some data from our first example).
Luckily in Java exists something called ExecutorService. I won’t waste your time and space here to describe all details of ExecutorService, now we can settle for a rudimentary simplification, that it can run some task in background and let you wait for the result, while the GUI is walking and talking :-) (For more details, look at the javadoc).

Example of ExecutorService usage:
ExecutorService executorService = Executors.newSingleThreadExecutor();
  
  Future<Long> future = executorService.submit(new Callable<Long>() {

   public Long call() throws Exception {
    System.out.println("Start of execution");
    long result = 0;
    for (long i = 0; i < 50000000l; i++) {
     Float f = (float) (Math.sin(i) * i); //doing some computation to slow it down
     result = i + result;
    }
    System.out.println("End of execution");
    return result;
   }
   
  });
  
  JFrame frame = new JFrame();
  JPanel panel = new JPanel();

  JProgressBar progressBar = new JProgressBar();
  progressBar.setIndeterminate(true);
  panel.add(progressBar);

  frame.setTitle("Executor service demo");
  frame.setContentPane(panel);
  frame.setSize(300, 80);
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  frame.setVisible(true);
  
  System.out.println("we are waiting for result, but swing is still painting");
  Long result = future.get();
  System.out.println("completed, result: " + result);

Showing user some progress

For now we leave threads behind and focus on user interface and letting user know, that something is happening.
The most common indication is progressbar and modern progressbar is something that rotates, so we will create rotating progressbar showing above the application. In combination with consuming mouse and keyboard events, this is used to block user screen and show, what is happening.

Example of JBusyComponent usage:

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.SwingUtilities;

import org.divxdede.swing.busy.JBusyComponent;

public class BusyComponentDemo {

 public static void main(String[] args) {
  SwingUtilities.invokeLater(new Runnable() {

   public void run() {
    JFrame frame = new JFrame();
    JPanel panel = new JPanel();
    JTree tree = new JTree();
    panel.add(tree);
    JBusyComponent<JPanel> busyComponent = new JBusyComponent<JPanel>(panel);
    busyComponent.setBusy(true);

    frame.setTitle("Busy application demo");
    frame.setContentPane(busyComponent);
    frame.setSize(300, 80);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
   }

  });
 }

}


In this example we use JBusyComponent as a modern progressbar (see JBusyComponent). The component is set as a contentPane of the main application frame and when set to busy it draws rotating icon over the frame, leaving the content visible and blocks all user input.

Now we can combine it together:

We have demonstrated how to run task in a background thread and wait for some result and how to notify user about running action. Now it’s time to combine all together and create user responsive application.

Example of responsive application:

JFrame frame = new JFrame();
  JPanel panel = new JPanel();

  JTree tree = new JTree();
  panel.add(tree);
  JBusyComponent<JPanel> busyComponent = new JBusyComponent<JPanel>(panel);

  frame.setTitle("Executor service demo");
  frame.setContentPane(busyComponent);
  frame.setSize(300, 200);
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  frame.setVisible(true);
  
  ExecutorService executorService = Executors.newSingleThreadExecutor();
  
  System.out.println("Setting busy");
  busyComponent.setBusy(true);
  Future<Long> future = executorService.submit(new Callable<Long>() {

   public Long call() throws Exception {
    System.out.println("Start of execution");
    long result = 0;
    for (long i = 0; i < 10000000l; i++) {
     Float f = (float) (Math.sin(i) * i); //doing some computation to slow it down
     result = i + result;
    }
    System.out.println("End of execution");
    return result;
   }
   
  });
  
  
  System.out.println("We are waiting for result, but swing is still painting");
  Long result = future.get();
  System.out.println("Completed, result: " + result);
  System.out.println("Not busy anymore");
  busyComponent.setBusy(false);


Looking much better now, right? Computation is running in background, code is waiting for the result of computation and GUI is live and responsive.

Conclusion

We created application, that runs some long task and while the task is being computed, user can see nice progress visualization and with some additional configuration can even cancel the running task.
And the post thought: please do not run your time-consuming tasks on the EDT, it is killing your application’s user experience.  





Populární příspěvky z tohoto blogu

CPU killer in Java with JavaFX GUI client

Motivation Few weeks ago I was searching for a source of a bug in one of our applications. The QA team suspected that slow computer could be a reason for this. You know that - developers have Core i7 CPU with 8 GB RAM (at least some of us :-) ) and never realize, that users can have slow machine, which reacts differently . And for that tiny moment you would need slow computer, just to see how it works and whether you can break it. But how to achieve this? The QA team does that by creating a virtual machine and setting it very low resources, so it acts very slow. Another option is to use some kind of CPU killer – those are great pieces of software, doing exactly what it sounds – killing your CPU. There are tons of complete solutions out there, but I didn’t want to deal with the licensing and our IT stuff yelling at me, what is that software that I installed by myself again. So I decided to break the rule “ Don’t reinvent the wheel ” again. Requirements At first, here is a list ...

Using JavaFX with Maven

JavaFX is exciting new framework from Oracle, which should replace Swing one day. But unfortunatelly it is not added to Java classpath, so we need to add it manually. And because I use Maven for all my projects (right now experimenting Gradle) we want to add it as a Maven dependency.  But javaFX jar is not in any public maven repository, thus we have to install it manually to local repository. This command does the installation: mvn install:install-file -Dfile=jfxrt.jar -DgroupId=com.oracle -DartifactId=javafx -Dversion=2.2.3 -Dpackaging=jar The command was executed in the directory, where the file  jfxrt.jar  is located (on Windows the path would be something like  c:\Program Files\Java\jdk1.7.0_09\jre\lib ) otherwise the full path has to be supplied. Latest version of JavaFX runtime is 2.2.3 (included in JDK 7u9) Once the installation is done, we can use regular maven dependency attribute: <dependency> <groupId>com.oracle</g...

Checking internet connection and connection to a particular server in Java

In AgroSense we need to know, if the computer is connected to the internet or not. And we don’t want to bother the user by throwing UnknownHostException or IOException also. So we need two things: Check the connection Notify everybody interested, that the connectivity changed When the first point is successfully implemented, the second one is easy – it is just a listener, so we will look only at the connectivity check problem. Solution For checking internet connectivity you need some host address. You will probably use the address, which you actually call, in our case it is www.openstreetmap.org. In first step, we check, if there is some internet connection, this is done in a method isOnlineFastCheck: private boolean isOnlineFastCheck() {         boolean check = false;         try {             InetAddress.getByName(hostName);             check = true;     ...