JavaRush /Java Blog /Random EN /Multithreading in Java: essence, "pluses" and frequent pi...

Multithreading in Java: essence, "pluses" and frequent 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 behind. 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. Rather, we need to change our mindset a bit. Before, your programs ran sequentially. Some lines of code followed others, some methods followed others, and in general everything was clear. First, calculate something, then print the result to the console, then terminate the program. To understand multithreading, it is better to think in terms of concurrency. Let's start with something very simple :)Multithreading in Java: essence, "pros" and frequent pitfalls - 1Imagine that your family is moving from one house to another. An important part of the move is to collect the books. You have accumulated a lot of books, and you need to put them in boxes. Now only you are free. Mom cooks food, brother collects clothes, and sister went to the store. At the very least, you manage alone, 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 has not changed: put the books in boxes. It just runs twice as fast. Why? Because the work is done in parallel. Two different “threads” (you and your sister) are doing the same task at the same time and if nothing changes, the time difference will be very large compared to the situation in which you would do everything alone.

Problems that Java multithreading solves

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

    Multithreading in Java: essence, "pros" and frequent pitfalls - 3

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

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

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 things 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 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. Threadand 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 MyFirstThreadand 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) MyFirstThreadthat inherit from Threadand 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-2and 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: Multithreading in Java: essence, "pros" and frequent pitfalls - 4 Imagine that thread-1 works with some Object-1, and thread-2 works with Object-2. The program is written like this:
  1. 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.
  2. 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.
Even without deep knowledge in multithreading, you can easily understand that none of this will work. Threads will never change places and will wait for each other forever. The error seems obvious, but it really isn't. You can easily allow it in the program. We will look at code examples that cause deadlock in the next lectures. By the way, Quora has a great real life example explaining what a deadlock is . “Some states in India 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.” Good thing to say! :) Now about the race condition - the state of the race. Multithreading in Java: essence, "pros" and frequent pitfalls - 5A race condition is a design error 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. Recall the thread launch example:
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.
Multithreading in Java: essence, "pros" and frequent pitfalls - 6
"Java Concurrency in Practice" was written back in 2006, but has not lost its relevance. It covers multi-threaded programming in Java, starting from the basics and ending with a list of the most common mistakes and anti-patterns. If you ever decide to become a multi-threaded 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