JavaRush /Java Blog /Random EN /Multithreading in Java: essence, advantages and common pi...

Multithreading in Java: essence, advantages and common pitfalls

Published in the Random EN group
Hello! First of all, congratulations: you have reached the topic of Multithreading in Java! This is a serious achievement, there is a long way to go. But get ready: this is one of the most difficult topics in the course. And the point is not that complex classes or many methods are used here: on the contrary, there are not even two dozen. It's more that you need to change your thinking a little. Previously, your programs were executed sequentially. Some lines of code followed others, some methods followed others, and overall everything was clear. First, calculate something, then display the result on the console, then terminate the program. To understand multithreading, it's best to think in terms of concurrency. Let's start with something very simple :) Multithreading in Java: essence, advantages and common pitfalls - 1Imagine that your family is moving from one house to another. An important part of moving is packing your books. You have accumulated a lot of books, and you need to put them in boxes. Now only you are free. Mom is preparing food, brother is collecting clothes, and sister has gone to the store. Alone you can manage, at least, and, sooner or later, you will even complete the task yourself, but it will take a lot of time. However, in 20 minutes your sister will return from the store, and she has nothing else to do. So she can join you. The task remained the same: put the books into boxes. It just runs twice as fast. Why? Because the work is done in parallel. Two different “threads” (you and your sister) are simultaneously performing the same task and, if nothing changes, the time difference will be very large compared to a situation in which you would do everything alone. If your brother completes his task soon, he can help you, and things will go even faster.

Problems that multithreading solves in Java

Essentially, Java multithreading was invented to solve two main problems:
  1. 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!

    Multithreading in Java: essence, advantages and common pitfalls - 3

    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 :)

  2. 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.

But, as you already read in the lecture, modern systems are very smart, and even on one computing core they are able to implement parallelism, or pseudo-parallelism, when tasks are performed alternately. Let's move from general things to specific ones and get acquainted with the main class in the Java library related to multithreading - java.lang.Thread. Strictly speaking, threads in Java are represented by instances of the class 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. Threadand 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 MyFirstThreadand 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) MyFirstThreadthat inherit from Threadand 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-2and 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: Multithreading in Java: essence, advantages and common pitfalls - 4 Imagine that thread-1 is working with some Object-1, and thread-2 is working with Object-2. The program is written like this:
  1. 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.
  2. 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.
Even without deep knowledge of multithreading, you can easily understand that nothing will come of it. The threads will never change places and will wait for each other forever. The error seems obvious, but in reality it is not. You can easily allow it into the program. We'll look at examples of code that causes a deadlock in the following lectures. By the way, Quora has an excellent real-life example explaining what a deadlock is . “In some states in India, they will not sell you agricultural land unless you are registered as a farmer. However, you will not be registered as a farmer if you do not own agricultural land.” Great, what can I say! :) Now about the race condition - the state of the race. Multithreading in Java: essence, pros and common pitfalls - 5A race condition is a design flaw in a multi-threaded system or application in which the operation of the system or application depends on the order in which parts of the code are executed. Remember the example with running threads:
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.
Multithreading in Java: essence, pros and common pitfalls - 6
“Java Concurrency in Practice” was written back in 2006, but has not lost its relevance. It covers multithreaded programming in Java, starting from the basics and ending with a list of the most common errors and antipatterns. If you ever decide to become a multithreaded programming guru, this book is a must read. See you at the next lectures! :)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION