Optimizing the performance of Java multithreaded programs: Tips for using the volatile keyword


The volatile keyword in Java is a very important concept in multi-threaded programming. It can ensure the visibility of variables among multiple threads, thus avoiding some thread safety problems. This article will introduce the relevant knowledge of volatile in Java in detail.

1. What is volatile

In Java, each thread has its own thread stack, which contains information such as local variables and method parameters. When a thread accesses a member variable of an object, it first reads the member variable from main memory into the thread's working memory, then operates on the variable, and finally writes the modified value back to main memory middle. Although this method improves the running efficiency of the program, it also brings some thread safety problems.

If multiple threads operate on the same variable at the same time, since each thread has its own working memory, data inconsistency between different threads may result. For example, a thread modifies a variable, but because the modification has not been written back to the main memory, another thread still gets the old value when reading the variable, which will cause some thread safety issues.

To solve this problem, Java provides the volatile keyword. Variables declared using volatile can ensure their visibility among multiple threads, that is, when one thread modifies the value of the variable, other threads can immediately see the modification. This is because the value of the volatile variable will be written directly into the main memory instead of being written into the working memory of the thread first, thereby avoiding the problem of data inconsistency between different threads.

Second, the characteristics of volatile

In addition to visibility, volatile has the following features:

1. Atomicity

The volatile variable can only guarantee the atomicity of its reading and writing, that is, each reading and writing operation is indivisible. However, if multiple threads write to a volatile variable at the same time, race conditions may still occur in these operations, resulting in data inconsistencies.

2. Sequence

A volatile variable can guarantee the sequentiality of its read and write operations, that is, the write operation precedes the read operation. This means that when one thread writes to a volatile variable, other threads see the latest value when they read the variable.

3. Visibility

A volatile variable can guarantee its visibility among multiple threads, that is, when a thread modifies the value of the variable, other threads can immediately see the modification. This is because the value of the volatile variable will be written directly into the main memory instead of being written into the working memory of the thread first, thereby avoiding the problem of data inconsistency between different threads.

Three, volatile usage scenarios

Volatile is usually used in the following situations:

1. Mark bit

When a variable is only used to mark a certain state, the volatile keyword can be used to ensure its visibility between multiple threads. For example, in Java, the interrupted() method of the Thread class is implemented using the volatile keyword.

2. Double Check Locking

Double-checked locking is a common singleton mode implementation, and the volatile keyword needs to be used in a multi-threaded environment to ensure its correctness. For specific implementation, please refer to the following code:

public class Singleton {
    private volatile static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

3. Counter

When a variable is used for counting, the volatile keyword can be used to ensure its visibility and atomicity among multiple threads. For example, in Java, the AtomicInteger class is implemented using the volatile keyword.

4. State variables

When a variable is used to represent a state, the volatile keyword can be used to ensure its visibility across multiple threads. For example, in Java, the Segment in ConcurrentHashMap is implemented using the volatile keyword.

In short, the volatile keyword is mainly used to ensure the visibility of variables among multiple threads, but it does not guarantee atomicity. If you need to ensure atomicity, you can use the synchronized keyword or the Lock interface to lock.

Fourth, the advantages and disadvantages of volatile

1. Advantages

(1) Visibility: The value of a volatile variable is visible among multiple threads, and the modification of a volatile variable by one thread will be seen by other threads immediately, thereby avoiding the problem of data inconsistency.

(2) Sequence: The read and write operations of volatile variables are sequential, which ensures that the write operation precedes the read operation, thereby avoiding problems such as dirty reads and phantom reads.

(3) Lightweight: Compared with the synchronized keyword or the Lock interface, the volatile keyword has no locking mechanism. Therefore, in the multi-threaded interaction scenario, using volatile variables can improve the execution efficiency of the program.

2. Disadvantages

(1) Atomic operations are not supported: Although volatile variables can guarantee the order and visibility of their read and write operations, they cannot guarantee atomicity. When multiple threads write to a volatile variable at the same time, race conditions may still occur, resulting in data inconsistency.

(2) Cannot replace the synchronized keyword: Although the volatile keyword can improve the execution efficiency of the program, in some complex multi-thread interaction scenarios, it is still necessary to use the synchronized keyword or the Lock interface to ensure thread safety.

5. How to use volatile correctly

In order to use the volatile keyword correctly, we need to follow the following principles:

1. Avoid composite operations

Compound operations refer to code blocks that contain multiple read and write operations, and such code blocks are prone to race conditions, which can lead to data inconsistencies. Therefore, when using volatile variables, compound operations should be avoided as much as possible, and each read and write operation should be performed separately.

2. Using atomic classes

If you need to ensure the atomicity of variables, you can use the atomic classes provided in Java, such as AtomicInteger, AtomicLong, etc. These atomic classes can guarantee the atomicity of variable read and write operations, thereby avoiding race conditions.

3. Avoid relying on the value of volatile variables

Because volatile variables do not guarantee atomicity, you should avoid relying on the correctness of their values ​​when using volatile variables. For example, when using volatile variables for counting operations, you should use atomic classes or the synchronized keyword to ensure the correctness of counting.

4. Declare volatile variable as final

Declaring a volatile variable as final ensures that it will not be modified after initialization, thereby avoiding some potential thread safety issues.

5. Use locks to ensure thread safety

Although the volatile keyword can improve the execution efficiency of the program, in some complex multi-threaded interaction scenarios, it is still necessary to use the synchronized keyword or the Lock interface to ensure thread safety.

Proper use of the volatile keyword can improve the execution efficiency of the program and avoid data inconsistency. However, it should be noted that it cannot guarantee atomicity and needs to be selected according to specific scenarios.

6. Comparison between volatile and synchronized

1. Principle

(1) volatile: Variables modified with the volatile keyword have visibility and order among multiple threads, but atomicity cannot be guaranteed.

(2) synchronized: Using the synchronized keyword can ensure that only one thread can execute the critical section code at the same time, thus ensuring thread safety and data consistency.

2. Applicable scenarios

(1) volatile: It is suitable for reading and writing operations on variables, and the visibility and order of variables need to be guaranteed between multiple threads. For example, counters, flags, etc.

(2) Synchronized: It is suitable for reading and writing operations on shared resources, and it is necessary to ensure mutual exclusive access between multiple threads. For example, producer consumer pattern, bank account transfer, etc.

3. Performance comparison

(1) volatile: Since volatile variables have no locking mechanism, using volatile variables can improve the execution efficiency of the program in the scenario of multi-thread interaction.

(2) synchronized: Since the synchronized keyword needs to acquire and release the lock, in the scenario of multi-thread interaction, using the synchronized keyword will reduce the execution efficiency of the program.

4. Recommendations for use

(1) If you need to ensure the visibility and order of variables, but do not need to guarantee atomicity, you can use the volatile keyword.

(2) If you need to ensure mutual exclusion between multiple threads and data consistency, you should use the synchronized keyword or the Lock interface.

(3) In some simple scenarios, you can use the volatile keyword instead of the synchronized keyword to ensure thread safety, thereby improving the execution efficiency of the program. However, in some complex scenarios, it is still necessary to use the synchronized keyword or the Lock interface to ensure thread safety.

Both the volatile keyword and the synchronized keyword are mechanisms used to ensure thread safety in Java, but their functions and applicable scenarios are different. In actual development, an appropriate mechanism should be selected according to specific requirements to ensure thread safety and data consistency.

7. Matters needing attention

1. Do not abuse the volatile keyword

Although the volatile keyword can improve the execution efficiency of the program, it cannot guarantee atomicity, nor can it replace the synchronized keyword to ensure thread safety. Therefore, when using the volatile keyword, you should carefully consider its applicable scenarios and avoid abuse.

2. Do not rely on the value of a volatile variable

Since volatile variables cannot guarantee atomicity, when using volatile variables for counting operations, you should use atomic classes or synchronized keywords to ensure the correctness of counting.

3. Declare volatile variable as final

Declaring a volatile variable as final ensures that it will not be modified after initialization, thereby avoiding some potential thread safety issues.

4. Pay attention to memory visibility

Although the volatile keyword can guarantee the visibility of variables, it is still necessary to pay attention to the issue of memory visibility in the scenario of multi-thread interaction. For example, thread A modifies the value of a shared variable, but this value has not been written into the main memory. When thread B reads this variable, it may read the old value, resulting in data inconsistency.

I am Amnesia, an otaku who loves technology. If you have any questions about the article, you can point it out in the message. Welcome to leave a message. You can also add my personal WeChat to study together and make progress together!
insert image description here

Guess you like

Origin blog.csdn.net/qq_42216791/article/details/129891763