Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the volatile keyword in java

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/02 Report--

This article introduces the relevant knowledge of "what is the volatile keyword in java". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

Today, let's discuss the knowledge point in Java concurrent programming: the volatile keyword.

This article mainly explains the volatile keyword from the following three points:

What is the volatile keyword?

What problem can the volatile keyword solve? What is the usage scenario?

The principle of volatile keyword implementation?

What is the volatile keyword?

The official JDK document at Sun describes volatile as follows:

The Java programming language provides a secondmechanism, volatile fields, that is more convenient than locking for somepurposes. A field may be declared volatile, in which case the Java Memory Modelensures that all threads see a consistent value for the variable.

That is, if a variable adds the volatile keyword, it tells the compiler and JVM's memory model that the variable is shared and visible to all threads, and each time JVM reads the most recently written value and makes it visible to all CPU. Volatile can guarantee the visibility of threads and provide some order, but it cannot guarantee atomicity. In the bottom layer of JVM, volatile is implemented by using memory barrier.

From this passage, we can see that volatile has two features:

Guarantee visibility, not atomicity

Prohibit instruction reordering

Atomicity and visibility

Atomicity means that the process in which one or more operations are either performed and executed will not be interrupted by any factor, or none of them will be performed. The nature is the same as transactions in the database, where a set of operations either succeed or fail. Take a look at these simple examples to understand atomicity:

I = = 0; / / 1J = I; / / 2i = j + 1; / / 4

Before looking at the answer, you can think about the above four operations, which are atomic operations? What are non-atomic operations?

The answer is:

1Murray-Yes: in Java, variable assignment operations to basic data types are all atomic operations (Java has eight basic data types, respectively, byte,short,int,long,char,float,double,boolean). 2Murray-No: contains two actions: reading I values, assigning I values to J3murf-No: including three actions: reading I values, iQuest 1, assigning i4 color values to i4 color-No: including three actions: reading j values Jroom1, which assigns the result of jiter1 to I

In other words, only simple reading and assignment (and must assign numbers to a variable, and mutual assignment between variables is not an atomic operation) is an atomic operation.

Note: since the previous operating system is 32-bit, 64-bit data (long type, double type) is 8 bytes in Java, occupying a total of 64 bits, so it needs to be divided into two operations to complete a variable assignment or read operation. With the increasing popularity of 64-bit operating systems, 64-bit data (long type, double type) are atomically processed in 64-bit HotSpot JVM implementations (because there is no clear provision in the JVM specification, it is not ruled out that other JVM implementations are handled in a 32-bit way).

In a single-threaded environment, we can think that the above steps are atomic operations, but in a multithreaded environment, Java only ensures that the assignment operations of the above basic data types are atomic, and other operations may make errors in the process of operation. For this reason, keywords such as lock and synchronized are introduced to ensure the atomicity of some operations in the multithreaded environment.

The volatile keyword above guarantees the visibility of variables, not atomicity. Atomicity has been said, so let's talk about visibility.

Visibility is actually related to the setting of the Java memory model: the Java memory model states that all variables are stored in main memory (thread shared area), and each thread has its own working memory (private memory). All operations on variables by a thread must be performed in working memory, not directly on main memory. And each thread cannot access the working memory of other threads.

Take a simple chestnut:

For example, in the above iTunes + operation, in Java, execute the iTunes + statement:

The execution thread first reads the I (original value) from the main memory to the working memory, then performs the operation + 1 in the working memory (the I value of the main memory remains the same), and finally flushes the operation result to the main memory.

The data operation is carried out in the private memory of the executing thread. After the thread executes the operation, it does not necessarily flush the operation result to main memory immediately (although it will definitely update the main memory eventually). The flushing to main memory action is triggered by CPU choosing an appropriate time. Assuming that the value is not updated to main memory, when other threads read it (and priority is to read data in working memory rather than main memory), the old value in main memory may still be the same, which may lead to an error in the operation result.

The following code is the test code:

Package com.wupx.test;/** * @ author wupx * @ date 2019-10-31 * / public class VolatileTest {private boolean flag = false; class ThreadOne implements Runnable {@ Override public void run () {while (! flag) {System.out.println ("execute Action"); try {Thread.sleep (1000L) } catch (InterruptedException e) {e.printStackTrace ();}} System.out.println ("task stops");}} class ThreadTwo implements Runnable {@ Override public void run () {try {Thread.sleep (2000L) System.out.println ("flag status change"); flag = true;} catch (InterruptedException e) {e.printStackTrace ();} public static void main (String [] args) {VolatileTest testVolatile = new VolatileTest (); Thread thread1 = new Thread (testVolatile.new ThreadOne ()) Thread thread2 = new Thread (testVolatile.new ThreadTwo ()); thread1.start (); thread2.start ();}}

The above results may not guarantee that the while in thread 1 will stop the loop immediately after thread 2 executes flag = true, because the flag state is first changed in thread 2's private memory, the time to refresh to main memory is not fixed, and thread 1 reads the value of flag in its own private memory, while thread 1's private memory flag is still not false, which may cause the thread to continue the while loop. The running results are as follows:

Perform operation flag status change task stop

To avoid the above unpredictable problem is to modify the shared variable modified by flag,volatile with the volatile keyword to ensure that the modified value will be updated to main memory immediately after the operation. When other threads need to manipulate the variable, it is not read from private memory, but forced to read the new value from main memory. That is, one thread modifies the value of a variable, and the new value is immediately visible to other threads.

Instruction reordering

Generally speaking, in order to improve the efficiency of the program, the processor may optimize the input code, which does not guarantee that the execution order of each statement in the program is the same as the order in the code. but it ensures that the final execution result of the program is consistent with the sequential execution of the code.

For example, the following code

Int I = 0; boolean flag = false;i = 1; / / 1flag = true; / / 2

The code defines an int variable, defines a boolean variable, and then assigns values to the two variables. In terms of code order, statement 1 comes before statement 2, so does JVM guarantee that statement 1 will be executed before statement 2 when actually executing this code? Not necessarily. Why? Instruction reordering (InstructionReorder) may occur here.

Statement 1 or statement 2 which executes first has no effect on the final program result, then it is possible that statement 2 will be executed first and statement 1 then.

Note, however, that although the processor reorders the instructions, it ensures that the final result of the program will be the same as the sequence of code execution, so how is it guaranteed? Look at the following example:

Int a = 10; / / 1int r = 2; / 2a = a + 3; / 3r = a * a; / / 4

The order of execution of this code may be 1-> 2-> 3-> 4 or 2-> 1-> 3-> 4, but the execution order of 3 and 4 will not change, because the processor will consider the data dependency between instructions when reordering, if an instruction Instruction2 must use the result of Instruction1, then the processor will ensure that Instruction1 will be executed before Instruction2.

Although reordering does not affect the results of program execution within a single thread, what about multithreading? Let's look at an example:

/ / 1String config = initConfig (); / / 1boolean inited = true; / / 2 / / Thread 2while (! inited) {sleep ();} doSomeThingWithConfig (config)

In the above code, statements 1 and 2 may be reordered because they have no data dependencies. If reordering occurs, statement 2 will be executed first during thread 1 execution, and thread 2 will think that the initialization work has been completed, then it will jump out of the while loop to execute the doSomeThingWithConfig (config) method, and the config will not be initialized, which will cause a program error.

As can be seen from the above, instruction reordering will not affect the execution of a single thread, but it will affect the correctness of thread concurrent execution.

So the meaning of the variable modified by the volatile keyword to prohibit reordering is:

When the program performs the read or write operation of the volatile variable, all the operations in front of it must have been carried out, and it can be seen from the latter operation that the subsequent operation must not have been carried out yet.

When optimizing an instruction, you cannot put the statement before the volatile variable after the read and write operation on the volatile variable, nor can you put the statement after the volatile variable before it.

Take a chestnut:

Xerox 0; / / 1 yearly 1; / / 2volatile z = 2; / / 3xtiny 4; / / 4 yearly 5; / / 5

If the variable z is a volatile variable, statement 3 will not be placed before statement 1 and statement 2, and statement 3 will not be placed after statement 4 and statement 5 when reordering instructions. But the order between statement 1 and statement 2, statement 4 and statement 5 is not guaranteed, and the volatile keyword guarantees that when statement 3 is executed, statement 1 and statement 2 must have been executed, and the execution results of statement 1 and statement 2 are visible to statements 3, 4, and 5.

Go back to the previous example:

/ / 1String config = initConfig (); / / 1volatile boolean inited = true; / / 2 / / Thread 2while (! inited) {sleep ();} doSomeThingWithConfig (config)

The previous example mentioned that it is possible that statement 2 will be executed before statement 1, which may result in an error when executing the doSomThingWithConfig () method.

Here, if you modify the inited variable with the volatile keyword, you can guarantee that the config has been initialized when statement 2 is executed.

Volatile application scenario

The synchronized keyword is to prevent multiple threads from executing a piece of code at the same time, which will greatly affect the execution efficiency of the program, while the volatile keyword performs better than synchronized in some cases, but note that the volatile keyword cannot replace the synchronized keyword, because the volatile keyword cannot guarantee the atomicity of the operation. In general, the following three conditions are required to use volatile:

Writing to a variable does not depend on the current value of the variable, or ensures that only a single thread updates the value of the variable

This variable will not be included in the invariance condition together with other state variables

There is no need to lock when accessing variables

The above three conditions only need to be guaranteed to be atomic in order to ensure that programs that use the volatile keyword can execute correctly at high concurrency. It is recommended that volatile is not used in getAndOperate situations, only the scenarios of set or get are suitable for volatile.

Two common scenarios are:

Status marker quantity

Volatile boolean flag = false;while (! flag) {doSomething ();} public void setFlag () {flag = true;} volatile boolean inited = false;// thread 1context = loadContext (); inited = true;// thread 2while (! inited) {sleep ();} doSomethingwithconfig (context)

DCL double check lock-singleton mode

Public class Singleton {private volatile static Singleton instance = null Private Singleton () {} / * when the getInstance () method is called for the first time, the instance is empty and the synchronization operation ensures that the multithreaded instance is unique * when the getInstance () method is called after the first time, the instance is not empty and does not enter the synchronization code block Reduced unnecessary synchronization * / public static Singleton getInstance () {if (instance = = null) {synchronized (Singleton.class) {if (instance = = null) {instance = new Singleton () } return instance;}}

Recommended reading: design pattern-singleton pattern

The reasons for using volatile have been mentioned in the above explanation of reordering. The main thing is that instance = new Singleton (), which is not an atomic operation, does three things in JVM:

Allocate memory to instance

Call the constructor of Singleton to initialize member variables

Point the instance object to the allocated memory inventory space (instance will be non-null after executing this step)

However, there is an optimization for instruction reordering in the JVM just-in-time compiler, which means that the second and third steps above cannot be guaranteed, and the final execution order may be 1-2-3 or 1-3-2. If it is the latter, thread 1 is preempted by thread 2 after executing 3 and before 2, and instance is already non-null (but has not been initialized), so thread 2 returns instance usage and reports a null pointer exception.

How is the volatile feature implemented?

Having talked about some of the use of the volatile keyword, let's take a look at how volatile ensures visibility and disables instruction reordering.

In the book "in depth understanding the Java Virtual Machine," it says:

Looking at the assembly code generated when adding the volatile keyword and not adding the volatile keyword, it is found that there is an extra lock prefix instruction when the volatile keyword is added.

Next, take a chestnut:

Volatile's Integer self-increment (iTunes +) is actually divided into three steps:

Read the volatile variable value to local

Increase the value of a variable

Write the value of local back to make it visible to other threads

The JVM instructions for these three steps are:

Mov 0xc (% R10),% R8d; Loadinc% R8d; Incrementmov% R8d 0x0 0xc (% R10); Storelock addl $0x0, (% rsp); StoreLoad Barrier

The lock prefix instruction is actually equivalent to a memory barrier (also known as a memory fence), which provides three functions:

It ensures that when instructions are reordered, the instructions behind them are not placed in front of the memory barrier, nor the previous instructions are placed behind the memory barrier; that is, when the instruction is executed to the memory barrier, all operations in front of it have been completed (reordering is prohibited).

It forces changes to the cache to be written to main memory immediately (for visibility)

If it is a write operation, it will invalidate the corresponding cache lines in other CPU (meet visibility)

The volatile variable rule is a kind of happens-before (first occurrence principle): the write operation to a variable occurs first in the subsequent read operation on that variable. (this feature can well explain why the DCL double-checked lock singleton mode is modified with the volatile keyword to ensure concurrency security.)

Summary

When a variable is declared to be of type volatile, both the compiler and the runtime will notice that the variable is shared and will not reorder the operations on the variable with other memory operations. Volatile variables are not cached in registers or invisible to other processors, so the most recently written value is always returned when a variable of type volatile is read.

The locking operation is not performed when accessing the volatile variable, which does not block the execution thread, so the volatile variable is a more lightweight synchronization mechanism than the sychronized keyword.

The locking mechanism ensures both visibility and atomicity, while the volatile variable only ensures visibility.

This is the end of the content of "what is the volatile keyword in java". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report