Interview articles-necessary for learning Java multi-threaded programming: in-depth understanding of volatile and synchronized

1 Overview

1.1 Volatile Overview

Volatile is a lightweight synchronization mechanism in Java, which is used to ensure the visibility of variables and prohibit instruction rearrangement. When a variable is declared as Volatile, any operation that modifies the variable is immediately seen by all threads. That is to say, variables modified by Volatile will be forced to refresh the modification to the main memory every time they are modified, which has good visibility and thread safety.

As can be seen from the figure above, in multi-threaded programming, threads do not directly operate the main memory, but copy the data in the main memory to the working memory, that is, manipulate variables in the form of a shared variable copy. When a variable is shared by multiple threads, if one thread modifies the value of the variable, another thread may get an outdated value when reading the variable. This is because of cache inconsistencies between threads.

This problem can be solved by using the volatile keyword. volatile guarantees that access to the variable is directed to the variable in main memory, rather than accessing the thread-local cache. Therefore, when a thread modifies the value of this variable, other threads can see the change immediately, avoiding the problem of cache inconsistency.

The specific implementation principle is that when the variable modified with the volatile keyword is used for read and write operations, the cache optimization of the CPU is prohibited, and each operation must directly read and write the main memory. At the same time, when performing a read operation, it is also forced to read the latest value from the main memory instead of using the thread local cache. In this way, variable access between multiple threads can be guaranteed to be synchronized and visible.

1.2 Overview of Synchronized

Synchronized is a heavyweight synchronization mechanism in Java to ensure thread safety and eliminate data races. When a method is declared as Synchronized, only one thread can access the method at a time, and other threads must wait. In this way, the problem of data inconsistency caused by multiple threads accessing shared resources at the same time can be avoided.

2. The difference between Volatile and Synchronized

(1) Volatile is a lightweight synchronization mechanism, and Synchronized is a heavyweight synchronization mechanism.

(2) Volatile is used to ensure the visibility of variables and prohibit instruction rearrangement, and Synchronized is used to eliminate data competition and ensure thread safety.

(3) Volatile cannot guarantee the atomicity of variables, and Synchronized can guarantee the atomicity of synchronized code blocks.

(4) The performance of Volatile is much higher than that of Synchronized, but it is only applicable to variables, while Synchronized is applicable to any type of object or code block.

(5) Variables modified by volatile are only used at the variable level (it will not cause thread blocking). The memory is refreshed when the thread reads and writes, and only visibility is guaranteed. volatile can also prohibit instruction rearrangement.

(6) synchronized lock variable or code segment, lock level (will cause thread blocking), can guarantee visibility and atomicity

3. Usage scenarios

3.1 Volatile usage scenarios

Due to the visibility of Volatile and the prohibition of instruction rearrangement, it is very useful in some specific scenarios, such as:

(1) Variables such as switches, status flags, and counters used to control threads;

(2) Used to publish some unchanging objects, such as instances in singleton mode;

(3) Used for performance tuning and avoiding lock competition, such as CAS algorithm.

Here is a simple example written in Java that demonstrates the volatile keyword:

 public class VolatileDemo {
        private volatile boolean isRunning = true;

        public void start() {
            System.out.println("Starting the thread...");
            new Thread(() -> {
                while (isRunning) {
                    // do some work here
                }
                System.out.println("Thread stopped.");
            }).start();
        }

        public void stop() {
            System.out.println("Stopping the thread...");
            isRunning = false;
        }

        public static void main(String[] args) throws InterruptedException {
            VolatileDemo demo = new VolatileDemo();
            demo.start();

            // wait for 3 seconds
            Thread.sleep(3000);

            demo.stop();
        }
    }

In the above example, we created a class called VolatileDemo and declared a volatile variable isRunning. This variable is used to indicate whether the thread should continue running. In the start() method, we start a new thread and check the value of the isRunning variable in the while loop. Since the isRunning variable is volatile, its value will always be synchronized when it is changed between different threads. In the stop() method, we set the value of the isRunning variable to false in order to stop the thread from running. In the main() method, we create a VolatileDemo object and call the start() method to start the execution of the thread. Then, we wait for 3 seconds, and then call the stop() method to stop the execution of the thread. Since the isRunning variable is of volatile type, the thread can detect the change of the isRunning variable in time, thereby stopping the execution of the thread.

3.2 Synchronized usage scenarios

Due to its mandatory synchronization, the Synchronized mechanism will also bring some performance overhead while ensuring thread safety and data integrity. Therefore, when using Synchronized, it is necessary to control the scope and frequency of synchronization. Proper use of Synchronized can improve the efficiency and reliability of the program. Scenarios for using Synchronized include:

(1) Access and modification of shared variables, such as synchronizing data in the case of multi-threading;

(2) Synchronize the constructor of the class instantiation to ensure thread safety during the instantiation process;

(3) Access and modification of static variables and methods, such as synchronizing static variables in the case of multi-threading.

Here is a simple example written in Java that demonstrates the synchronized keyword:

public class SynchronizedDemo {
    private int counter = 0;

    public synchronized void increment() {
        counter++;
    }

    public synchronized int getCounter() {
        return counter;
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedDemo demo = new SynchronizedDemo();

        // create multiple threads to increment counter
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.increment();
                }
            }).start();
        }

        // wait for all threads to finish
        Thread.sleep(5000);

        // print the final value of counter
        System.out.println("Final value of counter: " + demo.getCounter());
    }
}

In the above example, we created a class called SynchronizedDemo and declared a private variable counter. We added the synchronized keyword to the increment() method and getCounter() method to ensure that only one thread can access these methods at a time.

In the main() method, we create 10 threads to execute the increment() method concurrently, and each thread increments the counter 1000 times. Since the increment() method is synchronized, only one thread can access it at any time, avoiding data races and inconsistencies.

Finally, we wait for all threads to complete and print the final value of the counter. Since the getCounter() method is also synchronous, the value it returns will always be up to date and correct.

4. Precautions

(1) When using Volatile, you need to pay attention to visibility and atomicity, and the atomicity of multiple operations cannot be guaranteed.

(2) When using Synchronized, you need to pay attention to the scope of the synchronization block and the locking of the object to avoid deadlock and performance problems.

(3) In multi-thread programming, it is necessary to select an appropriate synchronization mechanism or use a combination of multiple synchronization mechanisms according to the actual situation to ensure the correctness and reliability of the program.

5. Relevant interview questions

  1. What is the difference between volatile and synchronized?

Answer: The volatile keyword can only guarantee the visibility of variables, but cannot guarantee atomicity and synchronization, while the synchronized keyword can not only guarantee atomicity and synchronization, but also ensure the visibility of variables.

  1. Can volatile replace synchronized?

Answer: No. Although volatile can guarantee the visibility of variables, it cannot guarantee atomicity and synchronization, while synchronized can guarantee these three characteristics.

  1. What is the difference between the implementation mechanism of synchronized and volatile?

Answer: synchronized is to ensure synchronization and atomicity by locking objects or classes, and its efficiency is relatively low; while volatile is to ensure the visibility of variables by prohibiting CPU cache optimization, and its overhead is relatively small, but it cannot Synchronization and atomicity are guaranteed.

  1. Under what circumstances do you need to use volatile?

Answer: When a variable is accessed by multiple threads, there may be a problem of cache inconsistency. In this case, you need to use volatile to ensure the visibility of the variable.

  1. What are the applicable scenarios of synchronized and volatile?

Answer: synchronized is suitable for ensuring the atomicity and synchronization of critical section code, while volatile is suitable for ensuring the visibility of variables. Therefore, if you need to perform complex operations and need to ensure atomicity and synchronization, you should use synchronized; and if you only need to ensure the visibility of variables, you can use volatile.

Guess you like

Origin blog.csdn.net/citywu123/article/details/130085036