Problems solved by multithreading
Multithreading was actually invented to achieve two important objectives:Do several things at the same time.
In the example above, different threads (family members) performed several actions in parallel: they washed dishes, went to the store, and packed things.
We can offer an example more closely related to programming. Suppose you have a program with a user interface. When you click 'Continue' in the program, some calculations should happen and the user should see the following screen. If these actions were performed sequentially, then the program would just hang after the user clicks the 'Continue' button. The user will see the screen with the 'Continue' button screen until the program performs all internal calculations and reaches the part where the user interface is refreshed.
Well, I guess we'll wait a couple of minutes!
Or we could rework our program, or, as programmers say, 'parallelize' it. Let's perform our calculations on one thread and draw the user interface on another. Most computers have enough resources to do this. If we take this route, then the program won't freeze up and the user will move smoothly between screens without worrying about what's happening inside. One doesn't interfere with the other :)
Perform calculations more quickly.
Everything is much simpler here. If our processor has multiple cores, and most processors today do, then several cores can handle our list of tasks in parallel. Obviously, if we need to perform 1000 tasks and each takes one second, one core can finish the list in 1000 seconds, two cores in 500 seconds, three in a little more than 333 seconds, etc.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
To create and run threads, we need to create a class, make it inherit the java.lang. Thread class, and override its run() method.
That last requirement is very important. It is in the run() method that we define the logic for our thread to execute.
Now, if we create and run an instance of MyFirstThread, the run() method will display a line with a name: the getName() method displays the thread's 'system' name, which is assigned automatically.
But why are we speaking tentatively? Let's create one and find out!
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Console output:
I'm Thread! My name is Thread-2
I'm Thread! My name is Thread-1
I'm Thread! My name is Thread-0
I'm Thread! My name is Thread-3
I'm Thread! My name is Thread-6
I'm Thread! My name is Thread-7
I'm Thread! My name is Thread-4
I'm Thread! My name is Thread-5
I'm Thread! My name is Thread-9
I'm Thread! My name is Thread-8
Let's create 10 threads (MyFirstThread objects, which inherit Thread) and start them by calling the start() method on each object. After calling the start() method, the logic in the run() method is executed.
Note: the thread names are not in order. It's weird that they weren't sequentially: Thread-0, Thread-1, Thread-2, and so on?
As it happens, this is an example of a time when 'sequential' thinking doesn't fit. The issue is that we've only provided commands to create and run 10 threads. The thread scheduler, a special operating system mechanism, decides their execution order.
Its precise design and decision-making strategy are topics for a deep discussion that we won't dive into right now. The main thing to remember is that the programmer cannot control the execution order of threads.
To understand the seriousness of the situation, try running the main() method in the example above a couple more times.
Console output on second run:
I'm Thread! My name is Thread-0
I'm Thread! My name is Thread-4
I'm Thread! My name is Thread-3
I'm Thread! My name is Thread-2
I'm Thread! My name is Thread-1
I'm Thread! My name is Thread-5
I'm Thread! My name is Thread-6
I'm Thread! My name is Thread-8
I'm Thread! My name is Thread-9
I'm Thread! My name is Thread-7
Console output from the third run:
I'm Thread! My name is Thread-0
I'm Thread! My name is Thread-3
I'm Thread! My name is Thread-1
I'm Thread! My name is Thread-2
I'm Thread! My name is Thread-6
I'm Thread! My name is Thread-4
I'm Thread! My name is Thread-9
I'm Thread! My name is Thread-5
I'm Thread! My name is Thread-7
I'm Thread! My name is Thread-8
Problems created by multithreading
In our example with books, you saw that multithreading solves very important tasks and can make our programs faster. Often many times faster. But multithreading is considered to be a difficult topic. Indeed, if used improperly, it creates problems instead of solving them. When I say 'creates problems', I don't mean in some abstract sense. There are two specific problems that multithreading can create: deadlock and race conditions. Deadlock is a situation where multiple threads are waiting for resources held by each other, and none of them can continue to run. We'll talk more about it in subsequent lessons. The following example will suffice for now:
- Thread-1 stops interacting with Object-1 and switches to Object-2 as soon as Thread-2 stops interacting with Object-2 and switches to Object-1.
- Thread-2 stops interacting with Object-2 and switches to Object-1 as soon as Thread-1 stops interacting with Object-1 and switches to Object-2.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Now imagine that the program is responsible for running a robot that cooks food!
Thread-0 gets eggs out of the fridge.
Thread-1 turns on the stove.
Thread-2 gets a pan and puts it on the stove.
Thread-3 lights the stove.
Thread-4 pours oil into the pan.
Thread-5 breaks the eggs and pours them into the pan.
Thread-6 throws the eggshells into the trash can.
Thread-7 removes the cooked eggs from the burner.
Thread-8 puts the cooked eggs on a plate.
Thread-9 washes the dishes.
Look at the results of our program:
Thread executed: Thread-0
Thread executed: Thread-2
Thread executed Thread-1
Thread executed: Thread-4
Thread executed: Thread-9
Thread executed: Thread-5
Thread executed: Thread-8
Thread executed: Thread-7
Thread executed: Thread-3
Is this a comedy routine? :) And all because our program's work depends on the execution order of the threads.
Given the slightest violation of the required sequence, our kitchen turns into hell, and an insane robot destroys everything around it. This is also a common problem in multithreaded programming. You'll hear about it more than once.
In concluding this lesson, I'd like to recommend a book about multithreading.

GO TO FULL VERSION