Get up to 80 % extra points for free! More info:

Lesson 1 - Multithreading in Java

In this article, we'll introduce multithreading in Java. I'll assume that you haven't used multithreading yet so I can say that all of your previous programs were running linearly. By that I mean command after command. There was only one command being executed at a time, and that command had to be completed in order to execute another. We say that only a single thread is running at a time (see further). Maybe you didn't know about the thread, but it's there and its entry point is the infamous `main () 'method. This approach is the simplest, but often not the best.

Imagine a situation where one thread is, for example, waiting for a user input. In the case of the single-threaded model, the entire program is waiting! And because users are generally slower than our program, there's an unpleasant waste of CPU time. Moreover, our program is very unstable. If anything happens to our thread (it crashes or gets blocked), the entire program will be affected again. There may also be times when we want to suspend a thread for a certain period of time. It's definitely not pleasant when we have to suspend the whole program because of that.

We can solve all these problems by... yes, you guessed it - multithreading.

Multithreading

A multithreaded program consists of two or more parts (threads), and although it may seem difficult at the start, it's quite easy to create a multithreaded application with Java's sophisticated support. We can say that a thread is a sort of self-executing sequence of commands. We can create threads freely (= define the sequence of commands) and manage them.

Creating a thread

There are practically 2 ways to create a thread:

  • By inheriting from the Thread class
  • by implementing the Runnable interface

These two approaches are equal, and it's up to each programmer to choose between them. In this article we'll show both of them.

The Runnable Interface

The Runnable interface is a functional interface. This is a new syntax of Java 8 and it's nothing but an interface with one abstract method. We'll show its benefits later. However, for the time being, the abstract run() method that defines this interface will be more important to us. This method is common to both of the above principles, and it represents the sequence of commands that the thread executes later.

The Thread Class

The Thread class implements the Runnable interface and will now be very important to us because it represents the thread itself. It defines a lot of constructors, but for now only 4 will be important:

public Thread()
public Thread(String name)
public Thread(Runnable target)
public Thread(Runnable target, String name)

As you can see, we can define the thread's name. This is a very useful thing that can help us debug our programs. This name can then be easily changed using the setName(String name) method, or retrieved by the getName() method.

We'll use the other two constructors to create a thread by implementing Runnable. First, we'll create a Runnable object, implement the run() method, and pass this object in the constructor when creating the thread. The thread will store the passed object and call the object's run() method automatically when its run() method is called.

An important method of this class is the start() method, which is a kind of thread entry point. This method performs preparatory work and then calls the run() method.

Extending the Thread class

Finally, we're going to create a new thread. Let's create a new project and name it as you want. Now we'll create a MyThread class:

class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
    System.out.println("Thread " + getName() + " is running");
        for(int i = 0; i < 4; ++i) {
            System.out.println("Thread " + getName() + ": " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ex) {
                System.out.println("Thread " + getName() + " suspended");
        return;
            }
        }
    System.out.println("Thread " + getName() + " terminated");
    }
}

The class inherits from the Thread class and overrides its run() method. The only new thing here is the sleep() method:

public static void sleep(long millis) throws InterruptedException

, which is declared by the Thread class and that suspends the thread from which it's been called for the time specified by the millis argument. The time is entered in milliseconds, which is a thousandth of a second. Now let's look at main():

public static void main(String[] args) {
    System.out.println("Main thread is running");
    MyThread myThread = new MyThread("Thread2");
    myThread.start();
    for(int i = 0; i < 4; ++i) {
        System.out.println("Main thread: " + i);
        try {
            Thread.sleep(750);
        } catch (InterruptedException ex) {
            System.out.println("Main thread suspended");
        return;
        }
    }
    System.out.println("Main thread terminated");
}

Now when we run the program, the output will look something like this:

Console application
Main thread is running
Main thread: 0
Thread Thread2 is running
Thread Thread2: 0
Thread Thread2: 1
Main thread: 1
Thread Thread2: 2
Main thread: 2
Thread Thread2: 3
Thread Thread2 terminated
Main thread: 3
Main thread terminated

However, this output may also vary. This is, of course, because the threads do not always run the same way. One may be faster, or better said, getting more processor time than another and this may change next time we run the program. This is what makes multithreading so unpredictable - that you never know how the threads will switch between each other. This process is called context switching and is handled by the operating system itself.

Context Switching

If two or more threads share a single processor (or, more precisely, a single core), this processor must switch between them while executing them. As I've already mentioned, this switching is handled by the operating system. Fortunately, we can also influence it ourselves explicitly. We use the thread priority to decide which thread will be allowed to execute (to which the processor time will be allocated). Each thread has an assigned priority represented by a number from 1 to 10. The default value is 5. We can set the priority using the setPriority() method or get it using the getPriority() method.

We can say that a higher-priority thread is executed prior to a lower-priority thread and it can force to suspend the lower-priority thread anytime (the stronger one survives :) ). But as I said, switching is handled by the operating system and each OS can handle threads and their priority differently. Therefore, you should not rely solely on automatic switching and try to care about it a little bit. For example, it's important to ensure that threads of the same priority suspended themselves from time to time. We can cause this elegantly by the static yield() method on the Thread class, which "takes" control of the currently running thread and passes it to the waiting thread with the highest priority.

Implementing the Runnable Interface

The second way to create a thread is to implement Runnable. As I said, this interface is a functional interface and therefore contains only one abstract method. In this case, of course, this is the run() method. Let's go over this Java 8 syntax quickly.

Functional interfaces and lambda expressions

The functional interface is a news of Java 8 and it's an interface that has only one abstract method. It should also be annotated with @FunctionalInterface for clarity.

For example, let's consider we want to create a Comparator object. In older Java versions we'd have to proceed as if creating an abstract class:

Comparator<String> com = new Comparator<String>() {

    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
};

You have to admit that it isn't very nice to write that much code for such a simple operation. Why do we actually have to specify which method we override when the interface has only one? Fortunately, this is no longer necessary. Thus, from Java 8, we can write the same thing using a lambda expression:

Comparator<String> com = (String a, String b) -> {
    return b.compareTo(a);
};

We simply specify the parameters of the abstract method, the -> operator, and the code block (the implementation of the abstract method). However, if there is only one statement in this block, we can omit both braces and return. It's also possible to omit the parameters' data type. The same code can look like this:

Comparator<String> com = (a, b) -> b.compareTo(a);

That's beautiful, isn't it? And if there was only one parameter in the parentheses, we could even omit those parentheses.

For those interested in more information, I have a link and one amazing and detailed article about Java 8.

So now we know what a functional interface is and we can easily create a thread by implementing Runnable. Remember the other two Thread class constructors? We'll use them here. In the main() method, we'll place the following code instead of creating the MyThread class:

Thread myThread = new Thread(() -> {
    System.out.println("Thread " + getName() + " is running");
        for(int i = 0; i < 4; ++i) {
            System.out.println("Thread " + getName() + ": " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ex) {
                System.out.println("Thread " + getName() + " suspended");
        return;
            }
        }
    System.out.println("Thread " + getName() + " terminated");
}, "Thread2");

Everything should be clear here. The program should behave as before. You may find this solution a bit harsh, it'd be probably better to use the abstract class concept instead of the lambda expression. But that's up to you.

I look forward to seeing you at the next multithreading lesson in Java, Multithreading v Javě - Daemon, join, and synchronized.


 

All articles in this section
Multithreaded Applications in Java
Skip article
(not recommended)
Multithreading v Javě - Daemon, join, and synchronized
Article has been written for you by Matej Kripner
Avatar
User rating:
No one has rated this quite yet, be the first one!
Activities