Problems that multithreading solves in Java
Essentially, Java multithreading was invented to solve two main problems:-
Perform multiple actions at the same time.
In the example above, different threads (i.e. family members) performed several actions in parallel: washed the dishes, went to the store, folded things.
A more “programmer” example can be given. Imagine that you have a program with a user interface. When the Continue button is clicked, some calculations should occur within the program, and the user should see the following interface screen. If these actions are carried out sequentially, after clicking the “Continue” button, the program will simply freeze. The user will see the same screen with a “Continue” button until all internal calculations are completed and the program reaches the part where the interface will begin to be drawn.
Well, let's wait a couple of minutes!
We can also remake our program, or, as programmers say, “parallelize.” Let the necessary calculations be performed in one thread, and interface rendering in another. Most computers have enough resources for this. In this case, the program will not be “stupid”, and the user will calmly move between interface screens without worrying about what is happening inside. It does not interfere :)
-
Speed up calculations.
Everything is much simpler here. If our processor has several cores, and most processors are now multi-core, our list of tasks can be solved in parallel by several cores. Obviously, if we need to solve 1000 problems and each of them is solved in a second, one core will cope with the list in 1000 seconds, two cores in 500 seconds, three in just over 333 seconds, and so on.
Thread
. That is, to create and run 10 threads, you will need 10 objects of this class. Let's write the simplest example:
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
To create and launch threads, we need to create a class and inherit it from the java.lang
. Thread
and override the method in it run()
. The last one is very important. It is in the method that run()
we prescribe the logic that our thread must execute. Now, if we create an instance MyFirstThread
and run it, the method run()
will print a line with its name to the console: the method getName()
prints the “system” name of the thread, which is assigned automatically. Although, actually, why “if”? Let's create and test!
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 We create 10 threads (objects) MyFirstThread
that inherit from Thread
and launch them by calling the object's method start()
. After calling a method , start()
its method starts working run()
, and the logic that was written in it is executed. Please note: the thread names are not in order. It's quite strange, why weren't they executed in turn: Thread-0
, Thread-1
, Thread-2
and so on? This is exactly an example of when standard, “sequential” thinking will not work. The fact is that in this case we only issue commands to create and launch 10 threads. In what order they should be launched is decided by the thread scheduler: a special mechanism inside the operating system. How exactly it is structured and on what principle it makes decisions is a very complex topic, and we will not dive into it now. The main thing to remember is that the programmer cannot control the sequence of thread execution. To realize the seriousness of the situation, try running the method main()
from the example above a couple more times. Second console output: 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 Third console output: 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 that multithreading creates
In the example with books, you saw that multithreading solves quite important problems, and its use speeds up the work of our programs. In many cases - many times. But it’s not for nothing that multithreading is considered a complex topic. After all, if used incorrectly, it creates problems instead of solving them. When I say “create problems,” I don’t mean something abstract. There are two specific problems that multithreading can cause: deadlock and race condition. Deadlock is a situation in which multiple threads are waiting for resources occupied by each other, and none of them can continue executing. We'll talk more about it in future lectures, but for now this example will suffice: Imagine that thread-1 is working with some Object-1, and thread-2 is working with Object-2. The program is written like this:- Thread-1 will stop working with Object-1 and switch to Object-2 as soon as Thread-2 stops working with Object 2 and switches to Object-1.
- Thread-2 will stop working with Object-2 and switch to Object-1 as soon as Thread-1 stops working with Object 1 and switches to Object-2.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Выполнен поток " + 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 the operation of a robot that prepares food! Thread-0 takes the eggs out of the refrigerator. Stream 1 turns on the stove. Stream-2 takes out a frying pan and puts it on the stove. Stream 3 lights a fire on the stove. Stream 4 pours oil into the pan. Stream 5 breaks the eggs and pours them into the frying pan. Stream 6 throws the shells into the trash bin. Stream-7 removes the finished scrambled eggs from the heat. Potok-8 puts scrambled eggs on a plate. Stream 9 washes dishes. Look at the results of our program: Thread-0 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-7 thread executed -3 Thread-6 thread executed. Is the script fun? :) And all because the operation of our program depends on the order in which the threads are executed. At the slightest violation of the sequence, our kitchen turns into hell, and a robot gone crazy destroys everything around it. This is also a common problem in multithreaded programming, which you will hear about more than once. At the end of the lecture, I would like to recommend you a book on multithreading.
GO TO FULL VERSION