Lecture 12

Nowadays computer users take it for granted that their systems can do several things at once. For instance they can edit a document while another program can play music. Even a single application is expected to be able to do several things at once. For example a word processor is expected to be responsive to the user actions at any time even if it has to do complex reformatting after every keystroke. This has not always been the case since the first operating systems did not allow more than one thing to happen at a time. In this lecture we will see how concurrent actions can be implemented in Java. In general this is a complex topic and it could be the subject of a whole course. Here we will just introduce the most basic concepts.

Processes and Threads, Concurrency and Parallelism

In concurrent programming, there are two basic units of execution: processes and threads. The process is mostly synonymous with an application. For instance you can run two different Java applications simultaneously, and the operating system will ensure that each application will run independently as if it is the only application running on the same computer. This is possible since the operating system emulates a seperate memory space for every application. In other words, it is not possible for one process to access the objects of another process. Furthermore, each process can run its own operations independently and simultaneously with the other processes.

How is that possible? The computer processor is only able to execute one instruction at a time, so it is not obvious how multiple applications can run simultaneously. Fortunately, most modern computers has two or more processors, and it is easy to imagine that if there are two running applications then each will be executed by a different processor. This would be a simple answer if there wasn't the practical problem that usually we want to do a lot of things at once, despite that there are only a small number of processors.

This brings us to the concepts of concurrency and parallelism. This concepts are often confused or taken as synonyms. The actual situation is as follows. Even if there is only one processor in the system, it still can execute several processes concurently. Each process runs for a fixed quota of time and after that it is interrupted to run another process for the next quota. In this way each process gets its turn to run for a short time and after that it is time for the next process in a roundabout fashion. If the time for switching between different processes is short enough this creates the illusion for simultaneous execution. This solution to the problem is called concurrency. When the system has more than one processors then the situation is just a little bit more complicated. In this case each processor can be used to run a process in parallel. If still there are more processes than processors, then we get a mixture between concurrency and parallelism. At any time there are two or more parallel processes but after a fixed time they are interrupted and the next turn processes are executed.

As we mentioned in the beginning, sometimes we want even a single process to do several things at once. This is the time to introduce threads. The characteristic of a process is that it has its own memory space and it owns one or more threads. Each thread corresponds to a different logical sequence of instructions. When we said in the last paragraph that processes take turns to be executed by the processor, we actually meant the threads for this processes. In the previous lectures we have talked only about algorithms which run sequentially, i.e. they have only one logical sequence. In this simple case each process owns only one thread which is started by running the main method in Java. However, it is possible to start another thread from the main thread of the process.

Threads in Java

The threads in Java are represented with objects of class 'Thread'. There are two ways to create a new thread. Either you create a subclass of the class Thread, or you create a class which implements the interface Runnable. The definition of Runnable is:

public interface Runnable {
    public void run();
}

When we implement the interface, the method run must contain the code that we want to execute in a separate thread.

As a very simple illustration we will implement a thread which just prints a message and after that completes its work:

public class HelloThread implements Runnable {
    public void run() {
        System.out.println("Hello");
    }
}

The thread can be started by creating an instance of class Thread and calling the method start:

Thread t = new Thread(new HelloThread());
t.start();

As an alternative to implementing the class Runnable you choose to subclass the class Thread. Then the code will look like this instead:

public class HelloThread extends Thread {
    public void run() {
        System.out.println("Hello");
    }
}
Thread t = new HelloThread();
t.start();

The advantage of the later is that you need to create only one object instead of two, but the disadvantage is that the class for the thread must extend Thread. Since in Java there must be only one base class, this is a strong restriction. The logic of your code might require that the base class is something else. In cases like this it is more flexible to implement the Runnable interface instead of fixing the base class to be Thread. Since the interface is more flexible we will use only this variant in the later examples.

When we create a thread we can also give a name to it. This is useful for debugging and it will also let us to see that the code in the run method is actually executed in another thread. Look at the following example:

public class HelloThread implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());

        Thread t = new Thread(new HelloThread(), "hello");
        t.start();
    }
}

This time we call the thread constructor with two arguments: the runnable object and a string which will be assigned as the name of the thread. We also use the static method Thread.currentThread() to get access to the current thread. The class Thread also has a method getName() which will return the name of the corresponding thread. By composing Thread.currentThread().getName(), we can get the name of the thread that is executing the current code. We print the name of the thread in two places: once in the beginning of the main method and a second time in the run method. When we run the code we will see that it will print:

main
hello

The first name is from the print in the main method. When the java interpreter runs an application, it creates a thread with name "main" and it runs the main method for the application in this thread. This is the reason why we see the output "main" from the first print. After that we create a new thread and we give it the name "hello". When the run method is executed, this will happen in the new thread which now has a different name. This is the reason why we get the second output "hello".

This first example is too simple to be useful. Usually threads are used for doing long computations. In such cases the computation is done in one thread while another thread is responsible for managing the user interface. In order to simulate a long computation we will define a thread which has an infinite loop in the run method. In addition we will also use the Counter class from the first lecture. For convenience its code is pasted here again:

public class Counter {
    private int count;

    public Counter() {
        count = 0;
    }

    public void increment() {
        count = count + 1;
    }

    public void reset() {
        count = 0;
    }

    public int getValue() {
        return count;
    }
}

Inside the loop, the thread will increment the counter and after each increment it will print the current value. The code for the example itself is bellow:

public class CounterThread implements Runnable {
    private Counter counter;

    public CounterThread(Counter my_counter) {
        counter = my_counter;
    }

    public void run() {
        for (;;) {
            counter.increment();
            System.out.println(Thread.currentThread().getName()+":"+counter.getValue());
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t = new Thread(new CounterThread(counter), "hello");
        t.start();
    }
}

With the new run method it is easy to demonstrate that indeed it is possible to have more than one threads running simultaneously. It is enough to create and start more than one thread. We just have to change the main method to the following:

public static void main(String[] args) {
    Counter counter = new Counter();
    Thread t1 = new Thread(new CounterThread(counter), "hello1");
    t1.start();
    Thread t2 = new Thread(new CounterThread(counter), "hello2");
    t2.start();
}

Since the two threads now have different names we can see from the generated output that the two threads take turns to print numbers in the terminal.

You might expect that the example from above will print distinct sequential numbers. However, if you look closer you will find that this is not always true. Most of the time the numbers are indeed distinct and sequential, but not always. Sometimes the same number might be repeated. It looks like there is no observable pattern and the behavour looks random. The situation becomes even worse if you have more than two threads. The reason for this is that when there are many threads there is no guarantee about the order in which the operations in the different threads will be executed. For instance one thread is incrementing the counter and after that it is printing the current value. However, in the mean time another thread might increment the counter as well. The result in general becomes unpredictable and it depends on many details in the operating system, the Java libraries, and the concrete hardware.

The source of the problem is that all threads share one and the same counter and there is no synchronization between the increments and the reading. Java has a built-in solution for this cases. We can declare methods as synchronized. This means it is not possible the same method for the same object to be called from more than one thread at a time. Look at this new version of synchronized counters:

public class SynchronizedCounter {
    private int count;

    public SynchronizedCounter() {
        count = 0;
    }

    public synchronized int increment() {
        count = count + 1;
        return count;
    }

    public synchronized void reset() {
        count = 0;
    }

    public int getValue() {
        return count;
    }
}

Now we have put the keyword 'synchronized' in front of the methods increment and reset. This means that if there is a thread that is currently executing this method for some object, then no other thread is allowed to execute it with the same object. If another thread attempts to execute it, then the second thread will be blocked until the execution in the first one is complete. After the completion, the blocked thread will be resumed. Note that we don't need to synchronize the method getValue since it does not change the state of the counter. The constructor is not synchronized either since when we create an object then in the beginning only the thread that calls the constructor has a reference to it. This means that no other thread is able to change it anyway. Actually if you try to declare a constructor as synchronized the compiler will report this as a syntax error.

Note also that now the method increment also returns the value of the counter just after the increment. The reason is that despite that the method is synchronized. If we call first the method increment and after that the method getValue, it is still possible for another thread to increment the counter again between the two method calls. This is easy to avoid if we return the new value directly from the method increment. The method getValue might be still useful as a way to read the current value from a thread which does not do any increments.

Now to take advantage of the synchronization in the new counter class we need to update the thread class:

public class CounterThread implements Runnable {
    private SynchronizedCounter counter;

    public CounterThread(SynchronizedCounter my_counter) {
        counter = my_counter;
    }

    public void run() {
        for (;;) {
            int count = counter.increment();
            System.out.println(Thread.currentThread().getName()+": "+count);
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread t1 = new Thread(new CounterThread(counter), "hello1");
        t1.start();
        Thread t2 = new Thread(new CounterThread(counter), "hello2");
        t2.start();
    }
}

Now if you check the output from this new version of the thread you will see that the printed numbeers are unique. They may still be in a strange order because this depends on the order in which the different threads do the increments.

In some situations we want to terminate threads before they have completed their work. For example the user might want to close the current application and then all running threads must be terminated. The class Thread offers two methods for this purpose: 'interrupt' and 'interrupted'. When the method 'interrupt' is called for some thread, then this sets a flag which tells that the thread has been asked to terminate. This does not lead to immediate termination, however. The method 'run' in the implementation of the thread must periodically call the static method 'interrupted'. If it returns true then the implementation of the 'run' method must simply return. This is illustrated with the following code which adds a 'JFrame' and a button. When the button is clicked this stops the running threads:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class CounterThread implements Runnable {
    private SynchronizedCounter counter;

    public CounterThread(SynchronizedCounter my_counter) {
        counter = my_counter;
    }

    public void run() {
        while (!Thread.interrupted()) {
            int count = counter.increment();
            System.out.println(Thread.currentThread().getName()+": "+count);
        }
    }

    public static void main(String[] args) {
        SynchronizedCounter counter = new SynchronizedCounter();
        final Thread t1 = new Thread(new CounterThread(counter), "hello1");
        t1.start();
        final Thread t2 = new Thread(new CounterThread(counter), "hello2");
        t2.start();

        JFrame frame = new JFrame();
        frame.setSize(100,100);
        frame.setTitle("CounterThread");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JButton button = new JButton("Stop");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                t1.interrupt();
                t2.interrupt();
            }
        });
        frame.add(button);
        frame.setVisible(true);
    }
}

Note that now the infinite loop in the implementation for 'run' is replaced with a while loop which check the result from 'interrupted'. The listener for the button just calls 'interrupt' for all threads.

Sometimes we want to have threads that don't need to do something all the time. For example we may want to have a thread which checks for new e-mail. Doing this continuously will add too much overhead both for our computer and for the e-mail server. It is a lot more reasonable to check once in a while, for example every five seconds. For this purpose the class Thread provides the method sleep. It suspends the execution of the current thread for a given period of time. During this period the operating system is free to give priority to the other threads in the system since it knows that the thread which called sleep is inactive. This allows the whole system to work more efficiently. In our toy example this can be illustrated by inserting a delay between every two printed numbers. Currently the numbers are printed too quickly for us to read them. This final version of the method run adds random delay after every increment:

public void run() {
    try {
        while (!Thread.interrupted()) {
            int count = counter.increment();
            System.out.println(Thread.currentThread().getName()+": "+count);
            Thread.sleep(100 + (int) (Math.random()*1000));
        }
    } catch (InterruptedException e) {
    }
}

The only thing that needs explanation here is the exception InterruptedException. It might happen that while the thread is in a sleeping mode, another thread calls the method 'interrupt'. In this case the sleeping thread will be resumed and the method 'sleep' will throw the exception. The implementation of the thread must handle the exception approriately. In our case we don't need to do anything since the exception will automatically terminate the loop and this will terminate the thread as well.