Problems that Java multithreading solves
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: they washed dishes, went to the store, put things away.
You can give a more "programmer" example. Imagine that you have a program with a user interface. When you click the Continue button, some calculations should happen inside 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 continue to see the same screen with a Continue button until all internal calculations are done and the program reaches the part where the UI will begin to draw.
Well, let's wait a couple of minutes!
And we can also remake our program, or, as programmers say, “parallelize”. Let the necessary calculations be performed in one thread, and the rendering of the interface - in another. Most computers have enough resources for this. In this case, the program will not be "stupid", and the user will calmly switch 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 multiple cores, and most processors are now multi-core, multiple cores can run our list of tasks in parallel. Obviously, if we need to solve 1000 tasks 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 333 seconds and so on.
Thread
. That is, to create and start the execution of 10 threads, you 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 form and run threads, we need to create a class, 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 write the logic that our thread must perform. Now, if we create an instance MyFirstThread
and run it, the method run()
prints a string with its name to the console: the method getName()
prints the “system” thread name, which is assigned automatically. Although, in fact, 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 run them by calling the method on the object start()
. After a method is called, start()
its method starts working run()
, and the logic that was written in it is executed. Note that the thread names are not in order. It's rather strange why they weren't executed in turn: Thread-0
. Thread-1
.Thread-2
and so on? This is just an example of when standard, "sequential" thinking will not work. The fact is that in this case we only give commands to create and launch 10 threads. In what order to run them is decided by the thread scheduler: a special mechanism within the operating system. How exactly it works and on what basis it makes decisions is a very complicated topic, and now we will not dive into it. The main thing to remember is that the programmer cannot control the sequence of execution of threads. To understand 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 of books, you saw that multithreading solves quite important tasks, and using it speeds up our programs. In many cases, many times. But multithreading is not without reason considered a complex topic. After all, if used incorrectly, it creates problems instead of solving them. When I say "make 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 several threads are waiting for resources occupied by each other, and none of them can continue executing. We will talk more about it in the following lectures, for now this example will suffice: Imagine that thread-1 works with some Object-1, and thread-2 works with Object-2. The program is written like this:- Thread-1 will stop working on Object-1 and switch to Object-2 as soon as Thread-2 stops working on Object-2 and switches to Object-1.
- Thread-2 will stop working on Object-2 and switch to Object-1 as soon as Thread-1 stops working on 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 work of a robot that cooks food! Thread-0 takes the eggs out of the refrigerator. Stream-1 turns on the stove. Potok-2 takes out a frying pan and puts it on the stove. Flux-3 lights a fire on the stove. Flow-4 pours oil into the pan. Flux-5 breaks the eggs and pours them into the pan. Thread-6 throws the shell into the trash can. Potok-7 removes the finished scrambled eggs from the fire. Thread-8 puts the scrambled eggs on a plate. Thread-9 washes the dishes. Look at the results of our program: 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-7 thread executed -3 Thread-6 executed Is the script funny? :) And all because the work of our program depends on the order of execution of threads. At the slightest violation of the sequence, our kitchen turns into hell, and a robot that has gone crazy destroys everything around it. This is also a common problem in multi-threaded programming, which you will hear about more than once. At the end of the lecture, I want to advise you a book on multithreading.