In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article mainly introduces the relevant knowledge of how to use volatile in Java, the content is detailed and easy to understand, the operation is simple and fast, and has a certain reference value. I believe you will gain something after reading this Java article on how to use volatile. Let's take a look at it.
one。 Related concepts of memory model
As we all know, when the computer executes the program, every instruction is executed in CPU, and in the process of executing the instruction, it is bound to involve reading and writing data. Because the temporary data in the running process of the program is stored in the main memory (physical memory), there is a problem. Because the execution speed of CPU is very fast, and the process of reading data from memory and writing data to memory is much slower than the execution speed of CPU, so if the operation of data is carried out through the interaction with memory at any time, it will greatly reduce the speed of instruction execution. So there is a cache in CPU.
That is, when the program is running, it will copy a copy of the data needed by the operation from the main memory to the CPU cache, then CPU can read and write the data directly from its cache when calculating, and then flush the data in the cache to the main memory when the operation is finished. Take a simple example, such as the following code:
1I = I + 1
When the thread executes this statement, it first reads the value of I from main memory, then copies a copy to the cache, then CPU executes instructions to add 1 to I, then writes the data to the cache, and finally refreshes the latest value of I in the cache to main memory.
There is no problem with this code running in a single thread, but there is a problem running in multiple threads. In multicore CPU, each thread may run in a different CPU, so each thread has its own cache at runtime (this problem can actually occur for a single-core CPU, but executed separately in the form of thread scheduling). In this paper, we take multicore CPU as an example.
For example, if there are two threads executing this code at the same time, if the initial value of I is 0, then we want the value of I to change to 2 after the two threads finish executing. But will this be the case?
There may be the following situation: initially, two threads read the value of I and put it in their respective CPU cache, and then thread 1 adds 1, and then writes the latest value of I, 1, to memory. At this time, the value of I in the cache of thread 2 is still 0. after adding 1, the value of I is 1, and then thread 2 writes the value of I to memory.
The value of the end result I is 1, not 2. This is the famous cache consistency problem. This kind of variable accessed by multiple threads is usually called a shared variable.
That is, if a variable is cached across multiple CPU (which usually occurs when programming with multiple threads), then there may be cache inconsistencies.
In order to solve the problem of cache inconsistency, there are generally two solutions:
1) by adding LOCK# locks on the bus
2) through cache consistency protocol
Both methods are provided at the hardware level.
In the early CPU, the problem of cache inconsistency was solved by adding LOCK# locks to the bus. Because CPU communicates with other components through the bus, if you add a LOCK# lock to the bus, that is to say, it blocks other CPU access to other parts (such as memory), so that only one CPU can use the memory of this variable. For example, in the above example, if a thread is executing I = I + 1, and if a LCOK# lock signal is sent on the bus during the execution of this code, then only after waiting for the code to be fully executed can other CPU read variables from the memory where the variable I is located and then operate accordingly. This solves the problem of cache inconsistency.
But there is a problem with the above approach, because other CPU cannot access memory while the bus is locked, resulting in inefficiency.
So there is a cache consistency protocol. The most famous is Intel's MESI protocol, which ensures that copies of shared variables used in each cache are consistent. Its core idea is: when CPU writes data, if you find that the variable is a shared variable, that is, a copy of the variable also exists in other CPU, it will send a signal to other CPU to set the cache row of the variable into an invalid state, so when other CPU needs to read this variable, it will re-read it from memory.
two。 Three Concepts in concurrent programming
In concurrent programming, we usually encounter the following three problems: atomicity, visibility, and order. Let's first take a look at these three concepts:
1. Atomicity
Atomicity: that is, 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.
A classic example is the problem of bank account transfer:
For example, transferring 1000 yuan from account A to account B must include two operations: subtract 1000 yuan from account An and add 1000 yuan to account B.
Imagine what would happen if these two operations were not atomic. If 1000 yuan is subtracted from account A, the operation is aborted suddenly. Then withdraw 500 yuan from B, withdraw 500 yuan, and then add 1000 yuan to account B. This will cause account A to subtract 1000 yuan, but account B does not receive the transferred 1000 yuan.
Therefore, these two operations must be atomic to ensure that there are no unexpected problems.
What will be the result of the same reflection in concurrent programming?
To take the simplest example, think about what would happen if the process of assigning a 32-bit variable was not atomic.
1I = 9
If a thread executes this statement, I will assume for a moment that assigning a 32-bit variable involves two processes: a low 16-bit assignment and a high 16-bit assignment.
Then a situation may occur: when a low 16-bit value is written and suddenly interrupted, and there is another thread to read the value of I, then the read is the wrong data.
two。 Visibility
Visibility means that when multiple threads access the same variable, one thread modifies the value of the variable, and other threads can see the modified value immediately.
For a simple example, take a look at the following code:
/ / Code executed by Thread 1
Int I = 0
I = 10
/ / Code executed by Thread 2
J = I
If thread 1 executes CPU1, thread 2 executes CPU2. From the above analysis, when thread 1 executes the sentence I = 10, it will first load the initial value of I into the CPU1 cache, and then assign a value of 10, then the value of I in the CPU1 cache becomes 10, but it is not immediately written to the main memory.
At this point, thread 2 executes j = I, which first reads the value of I in main memory and loads it into CPU2's cache. Note that the value of I in memory is still 0, so that the value of j is 0 instead of 10.
This is the visibility problem. After thread 1 modifies the variable I, thread 2 does not immediately see the value modified by thread 1.
3. Order
Orderliness: the order in which the program is executed according to the order of the code. For a simple example, take a look at the following code:
Int I = 0
Boolean flag = false
I = 1; / / statement 1
Flag = true; / / statement 2
The above 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 (Instruction Reorder) may occur here.
In general, the processor may optimize the input code in order to improve the efficiency of the program. It does not guarantee that the order of execution of each statement in the program is the same as that in the code, but it ensures that the final execution result of the program is consistent with that of the code sequence.
For example, in the above code, which statement 1 or statement 2 executes first has no effect on the final program result, then it is possible that statement 2 executes first and statement 1 then executes.
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; / / statement 1
Int r = 2; / / statement 2
A = a + 3; / / statement 3
R = aura; / / statement 4
This code has four statements, so one possible execution order is:
Is it possible to execute in this order: statement 2, statement 1, statement 4, statement 3
Impossible, because the processor will consider the data dependency between instructions when reordering, if an instruction Instruction 2 must use the result of Instruction 1, then the processor will guarantee that Instruction 1 will execute before Instruction 2.
Although reordering does not affect the results of program execution within a single thread, what about multithreading? Let's look at an example:
/ / Thread 1:
Context = loadContext (); / / statement 1
Inited = true; / / statement 2
/ / Thread 2:
While (! inited) {
Sleep ()
}
DoSomethingwithconfig (context)
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 (context) method, and the context will not be initialized, which will cause an error in the program.
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.
In other words, for concurrent programs to execute correctly, atomicity, visibility, and orderliness must be ensured. As long as one is not guaranteed, it may cause the program to run incorrectly.
III. Java memory model
I talked about the memory model and some problems that may arise in concurrent programming. Let's take a look at the Java memory model and study what guarantees the Java memory model provides for us and what methods and mechanisms are provided in java to ensure the correctness of program execution when we do multithreaded programming.
In the Java virtual machine specification, we try to define a Java memory model (Java Memory Model,JMM) to shield the memory access differences of each hardware platform and operating system, so as to achieve the consistent memory access effect of Java programs on various platforms. So what does the Java memory model specify? it defines the access rules for variables in the program and, to a large extent, the order in which the program executes. Note that in order to achieve better execution performance, the Java memory model does not limit the execution engine to use the processor's registers or caches to speed up instruction execution, nor does it restrict the compiler to reorder instructions. In other words, there are cache consistency problems and instruction reordering problems in the java memory model.
The Java memory model states that all variables are stored in main memory (similar to the physical memory mentioned earlier), and each thread has its own working memory (similar to the previous cache). 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.
To take a simple example: in java, execute the following statement:
1I = 10
The execution thread must first assign the cache line where the variable I resides in its worker thread before writing it to main memory. Instead of writing the value 10 directly to main memory.
So what guarantees does the Java language itself provide for atomicity, visibility, and orderliness?
1. Atomicity
In Java, reading and assigning variables of basic data types are atomic operations, that is, these operations are uninterruptible and are either performed or not performed.
Although the above sentence looks simple, it is not so easy to understand. Look at the following example I:
Analyze which of the following operations are atomic:
X = 10; / / statement 1
Y = x; / / statement 2
Xresume; / / statement 3
X = x + 1; / / statement 4
At first glance, some friends may say that the operations in the above four sentences are all atomic operations. In fact, only statement 1 is an atomic operation, and the other three statements are not atomic operations.
Statement 1 assigns the value 10 directly to x, which means that the thread executing the statement writes the value 10 directly to working memory.
Statement 2 actually contains two operations. it first reads the value of x, and then writes the value of x to working memory, although reading the value of x and writing the value of x to working memory are atomic operations. but together, it's not atomic.
Similarly, xdistinct + and x = xroom1 include three operations: read the value of x, add 1, and write a new value.
So only the operation of statement 1 in the above four statements is atomic.
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.
However, there is one thing to note here: on the 32-bit platform, reading and assigning 64-bit data needs to be done through two operations, and its atomicity can not be guaranteed. But it seems that in the latest JDK, JVM has guaranteed that reading and assigning 64-bit data is also atomic.
As you can see from the above, the Java memory model only ensures that basic reads and assignments are atomic operations. If you want to achieve atomicity in a wider range of operations, you can do so through synchronized and Lock. Because synchronized and Lock can guarantee that only one thread executes the block of code at any one time, there is naturally no atomicity problem, thus ensuring atomicity.
two。 Visibility
For visibility, Java provides the volatile keyword to ensure visibility.
When a shared variable is modified by volatile, it ensures that the modified value is immediately updated to main memory, and when another thread needs to read it, it reads the new value in memory.
On the other hand, ordinary shared variables can not guarantee visibility, because it is uncertain when ordinary shared variables will be written to main memory after they are modified, and when other threads read them, the old values in memory may still be the same, so visibility cannot be guaranteed.
In addition, visibility can also be guaranteed through synchronized and Lock, synchronized and Lock can ensure that only one thread acquires the lock and executes the synchronous code at a time, and flushes the changes to the variable to main memory before releasing the lock. So visibility can be guaranteed.
3. Order
In the Java memory model, the compiler and processor are allowed to reorder instructions, but the reordering process does not affect the execution of single-threaded programs, but affects the correctness of multithreaded concurrent execution.
In Java, you can use the volatile keyword to ensure a certain degree of "ordering" (the details are described in the next section). In addition, ordering can be ensured through synchronized and Lock. Obviously, synchronized and Lock guarantee that a thread executes synchronous code at every moment, which is equivalent to letting threads execute synchronous code sequentially.
In addition, the Java memory model has some innate "orderliness", that is, orderliness that can be guaranteed without any means, which is often referred to as the happens-before principle. If the execution order of the two operations cannot be deduced from the happens-before principle, then they cannot guarantee their order, and the virtual machine can reorder them at will.
The following is a specific introduction to the happens-before principle (the principle of first occurrence):
Program order rule: within a thread, according to the code order, the operation written in front occurs first in the operation written later.
Locking rule: a unLock operation occurs first on the same lock forehead lock operation.
Volatile variable rule: the write operation to a variable occurs first in the subsequent read operation on the variable
Transfer rule: if operation An occurs first in operation B, and operation B occurs in operation C first, it can be concluded that operation An occurs in operation C first.
Thread startup rule: the start () method of the Thread object occurs first in each action of this thread
Thread interrupt rule: the call to the thread interrupt () method occurs first in the code of the interrupted thread to detect the occurrence of the interrupt event
Thread termination rule: all operations in a thread first occur in the termination detection of the thread. We can detect that the thread has terminated execution through the termination of the Thread.join () method and the return value of Thread.isAlive ().
Object termination rule: the initialization of an object occurs first at the beginning of its finalize () method
These eight principles are extracted from "in-depth understanding of the Java virtual machine".
Among the eight rules, the first four rules are more important, and the last four rules are obvious.
Let's explain the first four rules:
As far as program order rules are concerned, my understanding is that the execution of a piece of program code appears to be orderly in a single thread. Note that although it is mentioned in this rule that "the operation written in front occurs first in the operation written later," this should be the order in which the program appears to be executed in code order. because the virtual machine may reorder the program code. Although reordering is carried out, the final result of execution is consistent with the sequential execution of the program, which only reorders instructions that do not have data dependencies. Therefore, it is important to understand that program execution appears to be orderly in a single thread. In fact, this rule is used to ensure the correctness of the execution result of the program in a single thread, but it cannot guarantee the correctness of the execution of the program in multi-thread.
The second rule is also relatively easy to understand, that is, if the same lock is in a locked state, either in a single thread or in a multithread, the lock must be released before the lock operation can proceed.
The third rule is a more important rule, and it is also the content that will be emphasized later. The intuitive explanation is that if a thread writes a variable first and then a thread reads it, then the write operation must occur first in the read operation.
The fourth rule actually embodies the transitivity of the happens-before principle.
four。 In-depth analysis of the volatile keyword
I've talked about a lot of things before, which are actually laying the groundwork for the volatile keyword, so let's get to the topic.
Two-layer semantics of 1.volatile keyword
Once a shared variable (member variable of a class, static member variable of a class) is modified by volatile, there are two layers of semantics:
1) ensures the visibility of different threads when operating on this variable, that is, one thread modifies the value of a variable, and the new value is immediately visible to other threads.
2) instruction reordering is prohibited.
First look at a piece of code, if thread 1 executes first, thread 2 then executes:
/ / Thread 1
Boolean stop = false
While (! stop) {
DoSomething ()
}
/ / Thread 2
Stop = true
This code is a typical piece of code, and many people may use this marking method when interrupting threads. But in fact, will this code work completely correctly? That is, will the thread be interrupted? Not necessarily, perhaps most of the time, this code can interrupt the thread, but it is also possible that the thread cannot be interrupted (although this is unlikely, as long as this happens, it will cause a dead loop).
Let's explain why this code can cause threads to be uninterruptible. As explained earlier, each thread has its own working memory while running, so thread 1 will put a copy of the value of the stop variable in its own working memory at run time.
So when thread 2 changes the value of the stop variable, but before it has time to write to main memory, thread 2 goes on to do something else, so thread 1 will continue to loop because it does not know the change of thread 2 to the stop variable.
But after decorating with volatile, it becomes different:
First: use the volatile keyword to force the modified value to be written to main memory immediately
Second: if you use the volatile keyword, when thread 2 modifies, the cache line of the cache variable stop in the working memory of thread 1 will be invalid (if reflected in the hardware layer, the corresponding cache line in the L1 or L2 cache of CPU is invalid)
Third: because the cache line of the cache variable stop in thread 1's working memory is invalid, thread 1 will go to main memory to read the value of the variable stop again.
Then when thread 2 modifies the stopvalue (of course, there are two operations here, modify the value in thread 2's working memory, and then write the modified value to memory), it will invalidate the cache line of the cache variable stop in thread 1's working memory, and then thread 1 will find its cache line invalid when it reads it. It will wait for the corresponding main memory address to be updated, and then go to the corresponding main memory to read the latest value.
Then thread 1 reads the latest and correct value.
Does 2.volatile guarantee atomicity?
We know from the above that the volatile keyword ensures the visibility of the operation, but can volatile guarantee that the operation on the variable is atomic?
Let's look at an example:
Public class Test {
Public volatile int inc = 0
Public void increase () {
Inc++
}
Public static void main (String [] args) {
Final Test test = new Test ()
For (int iTuno Bandi)
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.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.