Threads and Multithreading – Part 1

Threads - images of multicoloured threads

For the last few posts, we’ve looked at naming things in our code. Now we’ll move on to something entirely different and look at threads and multi-threading in Java.

Multitasking is the ability of an operating system to have more than one program executing simultaneously. Unless we have a machine with multiple processors, what really happens is that the operating system allocates small blocks of time to each application in turn, at a speed that makes it appear as though all the programs are executing simultaneously.

Types of Multitasking

Multitasking can be done in various ways:

Pre-emptive multitasking

This is when the operating system interrupts applications (without consulting with them first), in order to run another application waiting in the queue for resources. The operating system then runs this process for a limited amount of time (a time slice). When the operating system determines that the process has completed its allocated time slice, it gets taken off the run queue.

There are various approaches to time slicing:

  • Round robin allocates the same time slice size to all processes, regardless of priority.
  • Priority-based time slicing allocates a length of time depending on the priority of the process.
  • Pre-emptive time slicing also allocates a length of time depending on the priority of the process. With this approach, however, the operating system will also pre-empt a lower priority process if a higher-priority process joins the queue, i.e. becomes runnable.

Cooperative multitasking

This is when the processes are only interrupted when they are willing to yield control. This approach depends on all processes being well behaved. If not, a single badly-behaved process can hog resources and bring the entire system to a standstill.

Threads

Some applications can benefit from running several tasks at once. We might want to do this to make the application more responsive, or to do some background processing. The common mechanism for doing this is to create a number of threads that run in parallel with the main thread.

Multiple processes can run within one application. These are commonly known as threads. Other terms commonly used are threads of execution, lightweight processes, or execution contexts.

Java provides basic support for writing multithreaded programs via the Thread class and Runnable interface, the synchronized and volatile language keywords, and the wait(), notify() and notifyAll() methods of the Object class.

Potential Pitfalls

While multithreading can have many advantages, it also increases the possibility of bugs. There are several pitfalls to be aware of:

  • Safety: threads can attempt to simultaneously access or modify data. This can lead to data corruption and loss of data integrity.

  • Liveness and deadlock: it is possible that one thread can be waiting for another thread to execute, while that thread is waiting for the first thread to execute.

  • Non-determinism: if a non-threaded program is run, its behaviour will be identical every time. However, when a threaded program runs, the behaviour may be different each time. The Java Virtual Machine does not guarantee the order of execution of unsynchronized threads. This can make troubleshooting difficult.

  • Performance: it is possible for a threaded program to perform more poorly than a non-threaded program. This is due to the overhead involved in creating threads, switching context between threads, synchronizing threads, etc.

Creating Threads

How do we go about creating threads? There are two basic ways: extending the Thread class and implementing the Runnable interface.

The Thread class has a run() method which we can override to get the functionality we require. The Runnable interface defines a run() method which we need to implement.

We can think of the run() method as a kind of “main” method for our thread process.

Subclassing the Thread Class

If we create a thread by extending the Thread class, we must override the run() method. For example:

public class MyThread extends Thread {

    // Override the empty run() method of the Thread class
    public void run() {
        statements;
    }
}

In general, we only extend classes to add extra functionality, and not merely to implement some default behaviour we need. Extending the Thread class just to create an object that can be run as a thread isn’t good object-oriented design.

Remember that we extend a class to create a super-duper version of the class with lots of extra functionality. The Thread class should only be extended if we want to add extra methods to our inherited Thread classes that other classes in our application can use.

Remember too that Java only supports single inheritance. We can only extend a single class. We cannot add multithreading behaviour directly to an existing subclass by also extending Thread.

We could, however, add multithreading behaviour to an existing class (or an extended version of it) by using anonymous inner classes:

public class MyClass extends SomeOtherClass {

    private Thread thread;

    public void foo() {
        // Creating an anonymous inner class of type Thread
        thread = new Thread() {
            // Overriding run method
            public void run() {
                statements;
            }
        };
        thread.start();
    }
}

Implementing the Runnable Interface

The preferred way to create a thread is to implement the Runnable interface in an existing class, and implement the run() method:

public class MyRunnableClass implements Runnable {

    // Implement the run() method of the Runnable interface
    public void run() {
        statements;
    }
}

The advantage of this method is that we can add multithreading behaviour to any existing class.

The run() Method

We think of the run() method as the thread’s “main()” method. After starting a thread in a program, the code in the run() method of that particular thread is executed by a newly created sub-process, which will then execute in parallel with other threads (including the main thread of the program).

A typical implementation of a run() method follows:

public void run() {
    // stillMoreWorkToDo is a boolean variable that can be
    // reset by another method in the same class to end the loop.
    while (stillMoreWorkToDo) {
        ‹statements;›
    }
    // Exit and terminate thread
}

Starting Threads

To start a thread, we use the following code:

// If extending the Thread class:
Thread t = new MyThread();
t.start();          // Creates a new thread and executes run()

// If implementing the Runnable interface:
Runnable r = new MyRunnableClass();
Thread t = new Thread(r);
t.start();          // Creates a new thread and executes run()

We have to use the start() method in the Thread class to run a thread. We cannot simply call the run() method when we want to run a thread. Well, we could, but it will simply execute the code in the run() method directly in the main thread of execution, and it will not create a parallel thread.

The start() method spawns a new operating system level thread. The code in the run() method is passed to it to be executed in this new thread of execution. The actual low-level code of spawning a new thread is primarily implemented by operating system specific native C code. As such, we should never need to override the start() method.

Conclusion

This is the tip of the iceberg of threads. There’s lots more to come!

In the next few posts we’ll look at the lifecycle of threads, how to control them, the basic use of the synchronized keyword, and the wait() and notify() methods of the Object class. Then we’ll look at the exciting classes in the java.util.concurrent package which make creating, running and controlling threads vastly easier.

Please share your comments and questions.

Until then, stay safe and keep coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.