Hello! Today we will continue to consider the features of multi-threaded programming and talk about thread synchronization.
What is “synchronization”? Outside of the programming realm, this refers to some kind of setup that allows two devices or programs to work together. For example, a smartphone and computer can be synchronized with a Google account, and a personal account on a website can be synchronized with accounts on social networks in order to log in using them. Thread synchronization has a similar meaning: it is setting up how threads interact with each other. In previous lectures, our threads lived and worked separately from each other. One was counting something, the second was sleeping, the third was displaying something on the console, but they did not interact with each other. In real programs such situations are rare. Several threads can actively work, for example, with the same set of data and change something in it. This creates problems. Imagine that multiple threads are writing text to the same location, such as a text file or the console. This file or console in this case becomes a shared resource. Threads do not know about each other’s existence, so they simply write down everything they can manage in the time that the thread scheduler allocates to them. In a recent lecture of the course, we had an example of what this would lead to, let’s remember it:
The reason lies in the fact that the threads worked with a shared resource, the console, without coordinating actions with each other. If the thread scheduler has allocated time to Thread-1, it immediately writes everything to the console. What other threads have already managed to write or haven’t managed to write is not important. The result, as you can see, is disastrous. Therefore, in multi-threaded programming, a special concept mutex was introduced (from the English “mutex”, “mutual exclusion” - “mutual exclusion”) . The purpose of a mutex is to provide a mechanism so that only one thread has access to an object at a certain time. If Thread-1 has acquired the mutex of object A, other threads will not have access to it to change anything in it. Until object A's mutex is released, the remaining threads will be forced to wait. Real life example: imagine that you and 10 other strangers are participating in a training. You need to take turns expressing ideas and discussing something. But, since you are seeing each other for the first time, so as not to constantly interrupt each other and not descend into a hubbub, you use the “talking ball” rule: only one person can speak - the one who has the ball in his hands. This way the discussion turns out to be adequate and fruitful. So, a mutex, in essence, is such a ball. If an object's mutex is in the hands of one thread, other threads will not be able to access the object. You don't need to do anything to create a mutex: it's already built into the class
By the way! In the course lectures you have already seen examples of synchronized, but they looked different:
![Thread synchronization. Synchronized operator - 2](https://cdn.javarush.com/images/article/d6ea3443-4a0c-4633-b39a-1f793d8c5620/1024.jpeg)
Object
, which means every object in Java has it.
How the synchronized operator works in Java
Let's get acquainted with a new keyword - synchronized . It marks a certain piece of our code. If a block of code is marked with the synchronized keyword, it means that the block can only be executed by one thread at a time. Synchronization can be implemented in different ways. For example, create an entire synchronized method:public synchronized void doSomething() {
//...method logic
}
Or write a block of code where synchronization is carried out on some object:
public class Main {
private Object obj = new Object();
public void doSomething() {
//...some logic available to all threads
synchronized (obj) {
//logic that is only available to one thread at a time
}
}
}
The meaning is simple. If one thread enters a block of code that is marked with the word synchronized, it instantly acquires the object's mutex, and all other threads that try to enter the same block or method are forced to wait until the previous thread completes its work and releases the monitor. ![Thread synchronization. Synchronized operator - 3](https://cdn.javarush.com/images/article/f05403cc-d388-4dec-ac7f-4370fbabd4d0/1024.jpeg)
public void swap()
{
synchronized (this)
{
//...method logic
}
}
The topic is new for you, and of course there will be confusion with the syntax at first. Therefore, remember right away so as not to get confused later in the writing methods. These two writing methods mean the same thing:
public void swap() {
synchronized (this)
{
//...method logic
}
}
public synchronized void swap() {
}
}
In the first case, you create a synchronized block of code immediately upon entering the method. It is synchronized by object this
, that is, by the current object. And in the second example you put the word synchronized on the entire method. There is no longer any need to explicitly indicate any object on which synchronization is carried out. Once an entire method is marked with a word, this method will automatically be synchronized for all objects of the class. Let's not delve into the discussion of which method is better. For now, choose what you like best :) The main thing is to remember: you can declare a method synchronized only when all the logic inside it is executed by one thread at the same time. For example, in this case doSomething()
it would be an error to make the method synchronized:
public class Main {
private Object obj = new Object();
public void doSomething() {
//...some logic available to all threads
synchronized (obj) {
//logic that is only available to one thread at a time
}
}
}
As you can see, a piece of the method contains logic for which synchronization is not required. The code in it can be executed by several threads simultaneously, and all critical places are allocated to a separate synchronized block. And one moment. Let's look under the microscope at our example from the lecture with the exchange of names:
public void swap()
{
synchronized (this)
{
//...method logic
}
}
Please note: synchronization is carried out using this
. That is, for a specific object MyClass
. Imagine that we have 2 threads ( Thread-1
and Thread-2
) and only one object MyClass myClass
. In this case, if Thread-1
the method is called myClass.swap()
, the object's mutex will be busy, and Thread-2
when you try to call it, myClass.swap()
it will hang waiting for the mutex to become free. If we have 2 threads and 2 objects MyClass
- myClass1
and myClass2
- on different objects, our threads can easily simultaneously execute synchronized methods. The first thread does:
myClass1.swap();
The second one does:
myClass2.swap();
In this case, the synchronized keyword inside the method swap()
will not affect the operation of the program, since synchronization is carried out on a specific object. And in the latter case, we have 2 objects. Therefore, the threads do not create problems for each other. After all, two objects have 2 different mutexes, and their capture does not depend on each other.
Features of synchronization in static methods
But what if you need to synchronize a static method?class MyClass {
private static String name1 = "Olya";
private static String name2 = "Lena";
public static synchronized void swap() {
String s = name1;
name1 = name2;
name2 = s;
}
}
It is not clear what will serve as a mutex in this case. After all, we have already decided that every object has a mutex. But the problem is that to call a static method MyClass.swap()
we don’t need objects: the method is static! So, what is next? :/ Actually, there is no problem with this. The creators of Java took care of everything :) If the method that contains the critical “multithreaded” logic is static, synchronization will be carried out by class. For greater clarity, the above code can be rewritten as:
class MyClass {
private static String name1 = "Olya";
private static String name2 = "Lena";
public static void swap() {
synchronized (MyClass.class) {
String s = name1;
name1 = name2;
name2 = s;
}
}
}
In principle, you could have thought of this on your own: since there are no objects, then the synchronization mechanism must somehow be “hardwired” into the classes themselves. That’s how it is: you can also synchronize across classes.
GO TO FULL VERSION