Introduction to synchronized keyword (autumn move) -- Part I

Keywords: Java jvm Interview

1, Mental process

In Java Concurrent Programming, the synchronized keyword is undoubtedly a frequently asked question. During the interview, many interviewers pay more attention to your understanding of the synchronized keyword and its underlying. If you can answer it, it will undoubtedly add points. The following is what I learned through myself. I have combined the knowledge in the book with the summary. You are welcome to make corrections!

2, What is the synchronized keyword

In a multithreaded environment, there will be some problems when multiple threads access shared resources at the same time, and the synchronized keyword is used to ensure thread synchronization.

3, Visibility of JMM and Java Memory

Before we understand the underlying implementation principle of the synchronized keyword, we need to first understand the Java Memory Model (JMM) and see how the synchronized keyword works.

Of course, the local memory here is not real, but an abstract concept of the Java Memory Model (JMM also prefers a protocol in Java). It includes controller, arithmetic unit, cache, etc.

At the same time, the Java Memory Model stipulates that threads must operate on shared variables in their own local memory, and cannot directly operate shared variables in main memory. What's wrong with this memory model?

  1. Thread a obtains the value of shared variable x. at this time, there is no value of X in local memory a, so load the x value in main memory and cache it in local memory A. thread a modifies the value of X to 1 and flushes the value of X to main memory. At this time, the value of X in main memory and local memory is 1.

  2. Thread B needs to obtain the value of shared variable x. at this time, there is no value of X in local memory B. load the x value in main memory and cache it in local memory B. at this time, the value of X is 1. Thread B modifies the value of X to 2 and flushes it to main memory. At this time, the x value in main memory and local memory B is 2 and the x value in local memory A is 1.

  3. Thread A obtains the value of shared variable x again. At this time, the value of X exists in the local memory, so it directly obtains the value of X as 1 from local memory A, but at this time, the value of X in the main memory is 2. The so-called memory invisibility problem occurs.

The Java memory model can solve this problem through the synchronized keyword or volatile keyword. How can the synchronized keyword be solved? In fact, entering the synchronized block is to erase the variables used in the synchronized block from the thread's local memory, so that the variables used again in the synchronized block can not be obtained from the local memory, It needs to be obtained from the main memory, which solves the problem of invisible memory.

4, The difference between synchronized and volatile

In the morning, we said that synchronized can solve the problems in JMM, so what's the difference between them?

  • Volatile solves the problem of memory visibility, which makes all reads and writes to volatile variables directly write to main memory, that is, it ensures the visibility of variables.

  • Synchronized solves the problem of execution control. It prevents other threads from obtaining the monitoring lock of the current object. In this way, the code blocks protected by the synchronized keyword in the current object cannot be accessed by other threads, that is, they cannot be executed concurrently. Moreover, synchronized also creates a memory barrier. The memory barrier instruction ensures that all CPU operation results will be directly brushed into main memory, so as to ensure the memory visibility of the operation. At the same time, it also enables all operations of the thread of the lock (based on the happens before specification) to follow the operations of the thread that obtains the lock.

Main differences:

  1. volatile essentially tells the JVM that the value of the current variable in the register (working memory) is uncertain and needs to be read from main memory; synchronized locks the current variable. Only the current thread can access the variable, and other threads are blocked.

  2. volatile can only be used at the variable level; synchronized can be used at the variable. Method. And class levels

  3. volatile can only realize the modification visibility of variables and cannot guarantee atomicity; synchronized ensures the visibility and atomicity of variable modification

  4. volatile does not cause thread blocking; synchronized may cause thread blocking.

  5. Variables marked volatile are not optimized by the compiler; Variables marked synchronized can be optimized by the compiler (lock escalation mechanism).

5, The difference between Synchronized and Lock

  • Synchronized built-in Java keyword, Lock is a Java class

  • Synchronized cannot determine the status of obtaining the lock. Lock can determine whether the lock has been obtained

  • Synchronized will automatically release the lock. Lock must be released manually. If the lock is not released, it will cause deadlock

  • Synchronized assumes that thread A obtains the Lock and thread B waits. If thread A is blocked, thread B will wait all the time. The Lock lock can judge whether there is A Lock through tryLock

  • Synchronized reentrant Lock, non interruptible, non fair, Lock reentrant, judged, fair

  • Synchronized is suitable for locking a small number of code synchronization problems, and Lock is suitable for locking a large number of synchronization code

6, synchronized usage

  • Modify common synchronization methods

public class synTest implements Runnable {
    private volatile static int i = 0;   //shared resource
    private  synchronized void add() {
        i++;
    }
​
    public void run() {
        for (int j = 0; j < 10000; j++) {
            add();
        }
    }
    public static void main(String[] args) throws Exception {
        synTest syncTest = new synTest();
​
        Thread t1 = new Thread(syncTest);
        Thread t2 = new Thread(syncTest);
        t1.start();
        t2.start();
​
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

result:

20000

Let's change the code structure and check the results again:

public class synTest implements Runnable {
    private volatile static int i = 0;   //shared resource
    private  synchronized void add() {
        i++;
    }
​
    public void run() {
        for (int j = 0; j < 10000; j++) {
            add();
        }
    }
    public static void main(String[] args) throws Exception {
//        synTest syncTest = new synTest();
​
        Thread t1 = new Thread(new synTest());
        Thread t2 = new Thread(new synTest());
        t1.start();
        t2.start();
​
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

result:

< 20000

Although the add() method in the second example is also decorated with the synchronized keyword, because the two new synTest() operations establish two different objects, that is, there are two different object locks. Threads t1 and t2 use different object locks, so thread safety cannot be guaranteed. How should this situation be solved? Because the instance objects created each time are different, but there is only one class object, if the synchronized keyword acts on the class object, that is, the problem is solved by modifying the static method with synchronized.

  • Modified static method

Just use the static modifier before the add() method, that is, when synchronized acts on the static method, the lock is the current class object.

private static synchronized void add() {
        i++;
}

result:

2000

  • Decorating synchronization code

In some cases, the whole method body is relatively large and only a small part of the code needs to be synchronized. If the whole method body is synchronized directly, the code performance will become worse. At this time, only a small part of the code needs to be synchronized. The code is as follows:

public class synTest implements Runnable {
    private  static int i = 0;   //shared resource
​
    public void run() {
        synchronized (this){ // this represents the current object instance. synTest.class can also be used here; Represents a class object lock
            for (int j = 0; j < 10000; j++) {
                i++;
            }
        }
    }
    public static void main(String[] args) throws Exception {
        synTest syncTest = new synTest();
​
        Thread t1 = new Thread(syncTest);
        Thread t2 = new Thread(syncTest);
        t1.start();
        t2.start();
​
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

Output results:

20000

Posted by noaksey2000 on Fri, 03 Dec 2021 05:03:15 -0800