I believe many people are familiar with the volatile keyword and unfamiliar, familiar with the name is very familiar with, unfamiliar with his principles and usage is very unfamiliar, in recent days through a large number of information and books, finally to volatile have a certain understanding, write this blog to do a record, and then to reduce the cost of learning volatile why not guarantee atomicity.
Now our phones are multi-core, that is, there are several CPUs at the same time, each CPU has its own cache cache, because the speed of the CPU is particularly fast, and memory reading operations relative to the CPU speed of the operation is slow, so it will drag the CPU efficiency, The introduction of cache is to solve this problem, CPU first the required data from memory read to the cache, and then directly and cache to deal with, cache speed quickly, so you can ensure the efficiency of the CPU, when the cache data changes, then the changed data write back into memory.
First we analyze the process of multithreading accessing a common (without volatile modifier) variable
1.CPU1 and CPU2 First read the count variable into the cache, such as in-memory count=1, so now count is equal to 1 in the cache of two CPUs.
2.CPU1 and CPU2 each have a single thread to increment count, each of which has its own separate memory space to hold a copy of count.
3. After the thread 1 has been count=2 for copy count, thread 2 has been count=2 after the copy count has been self added
4. Thread 1 and Thread 2 write count of the operation back to count in cache
5.CPU1 and CPU2 write the count in cache back to count in memory.
So the problem is, the count of thread 1 and thread 2 operations is a copy of count, and the values of the two replicas are the same, so after two count=2, write back the memory. and the correct result should be count=3. That's the problem with multithreading concurrency . If the variable count is modified with volatile, can you solve the problem?
If a variable adds the volatile keyword, it tells the compiler and the JVM's memory model: This variable is shared and visible to all threads, and each time the JVM reads the most recently written value and makes its latest value visible to all CPUs. Let's take another look at the process of accessing a variable with the volatile modifier
When count is decorated with the volatile keyword, CPU1 the value of count, and when the memory is written back, the CPU2 count value has been updated, and you need to get the most recent value from memory.
Note: Here said CPU1 notice CPU2 is actually not rigorous, in fact, this is the cache consistency mechanism in its role, caching consistency mechanism will prevent the simultaneous modification of more than two processors cached memory area data, when the other processors write back to the locked cache row data, the cache line will be invalid, When CPU1 writes the new data back to memory, modifies the memory address of the data in memory by CPU2 the data transmitted on the bus to check whether the corresponding memory address of its cache line is modified, and if modified, sets the CPU2 cache line to an invalid state. When the processor modifies the data, it reads the data back into the CPU2 cache line from the system's memory. In fact, not CPU1 notice CPU2, but CPU2 himself to sniff.
In fact, as long as we understand the principle, how to say it does not matter, like many places are said volatile modified variables, threads and memory interaction, will not save the copy. In fact, the thread still saves the copy, except that the CPU gets the latest value from memory each time, and then writes back the memory immediately after changing the data, and looks like a thread directly interacts with memory.
Then the thread in CPU2 will then read the count's value from memory to update its cache if it needs to use count. This seems to solve our problem, in fact, the problem still exists, let us analyze:
For example, the current count=1,cpu1 and CPU2 cache count is equal to the 1,CPU1 in the thread 1 to the count of the self-operation, CPU1 then updates the value of count in memory and notifies CPU2 that the value of count has changed, and then CPU2 reads count=2 from memory to the cache, and thread 2 begins to perform the self-increment of count. And just as CPU2 has just read the value of Count back to the cache, CPU1 has updated the value of count, Count=3, and notifies CPU2, but at this point thread 2 is already executing and thread 2 has copied the count=2 into its own memory space. So even if CPU2 again update their cache in the count=3, also too late, thread 2 operation is his own memory space in the count copy, so thread 2 to count after the self increase operation, will count=3 and write back cache, CPU2 updates the count in memory. The value of count should be 4, but the CPU2 is still equal to 3 after the value of the count has been updated, so an error occurs. We are considering only two CPUs, but now there are already 8 nuclear phones in the market. If 8 CPUs work at the same time, the error will be more serious. Atomic how to guarantee atomic sex.
To know how atomic is guaranteed to be atomic, we have to understand what CAS is. CAS is a well-known unlocked algorithm (Compare and Swap). His core idea is that when multiple threads try to update the same variable with CAS, only one thread can update the value of the variable, while the other threads fail, and the failed thread does not hang, but is told that the competition has failed and can try again. CAS has 3 operands, memory value V, old expected value A, new value B to modify. If and only if the expected value A and memory value v are the same, the memory value V is modified to B, otherwise nothing is done.
Or the example above:
When CPU2 writes count=3 to memory, the 3 operands in CAs: v=3,a=2,b=3. The expected value of COUNT=3,CPU2 in the current memory should be count=2, but 3 is not equal to 2, so CPU2 failed in this competition. The memory value is no longer updated. When thread 2 executes again, CPU2 gets the most recent count value from memory. This ensures thread safety.
Atomic is the introduction of CAS without lock algorithm, let's take a look at Atomicinteger this class:
private volatile int value;
Atomicinteger class uses a volatile modified int type variable value to record our value, why use volatile?
/**
* Gets the current value.
*
@return The current value *
/public
final int get () {return
value;
}
There is a get () method in the Atomicinteger, and we've analyzed the variables decorated with the volatile keyword, and each time we get his value, we can get the most recent value in his memory, and since there is a get () method, there must be a set () method.
/**
* Sets to the given value.
*
* @param newvalue The new value
*
/public final void set (int newvalue) {
value = newvalue;
}
The set () method is simple enough to assign a new value to value, but as we analyzed it for half a day, he didn't guarantee atomic manipulation. Don't worry, the key is right here:
/**
* atomically sets to the given value and returns the old value.
*
* @param newvalue The new value
* @return The previous value
/public final int getandset (int newval UE) {for
(;;) {
int current = Get ();
if (Compareandset (NewValue)) return to current
;
}
is not very familiar with. Like we said just now, CAS algorithm, first call the Get () method to obtain the most recent value in memory, and then call the Compareandset method to update the value, we look at the Compareandset method
/**
* atomically Sets the value to the given updated value
* If the current value {@code =} The expected value.
*
* @param expect the expected value *
@param update the new value
* @return true if successful. False return indicates that
* The actual value is not equal to the expected value.
/Public
Final Boolean compareandset (int expect, int update) {return
unsafe.compareandswapint, Valueoffset, expect, update);
See, No. Compareandswap is not the CAS algorithm we just talked about. This will guarantee the atomic operation. Atomicinteger There are many ways to end up calling the Compareandswap method.
Like the ++value of atomic manipulation.
/** * atomically increments by one of the current
value.
*
* @return The updated value */public
final int incrementandget () {for
(;;) {
int current = Get ();
int next = current + 1;
if (Compareandset (next)) return to
next;
}
Atomic operating version of the value++
/** * atomically increments by one of the current
value.
*
* @return The previous value */public
final int getandincrement () {for
(;;) {
int current = Get ();
int next = current + 1;
if (Compareandset (next)) return to current
;
}
How to use the volatile keyword correctly.
Let's take a look at an example:
public class Volatiledemo {int x = 0;
Note: The b here is not volatile modified by Boolean B = false;
/** * Write operation */private void Write () {x = 5;
B = true; /** * Read operation/private void Read () {//if b=false, the loop will be infinite until b=true finishes, printing out the value of x while (!b
) {} System.out.println ("x=" +x); public static void Main (string[] args) throws Exception {final Volatiledemo Volatiledemo = new Volatiledemo
();
Thread 1 Execute write thread thread1 = new Thread (new Runnable () {@Override public void run () {
Volatiledemo.write ();
}
});
Thread 2 perform read operation thread thread2 = new Thread (new Runnable () {@Override public void run () {
Volatiledemo.read ();
}
});
We let thread 2 read operations first execute thread2.start ();
Sleep 1 milliseconds, in order to ensure that thread 2 executes TimeUnit.MILLISECONDS.sleep (1) than thread 1 first; And let the thread 1The write operation executes Thread1.start ();
Thread1.join ();
Thread2.join ();
Wait for thread 1 and thread 2 all over, print execution end System.out.println ("execution End"); }
}
Note that our B is not decorated with volatile, we started the thread 2 read operation, then started the thread 1 write operation, because thread 1 and thread 2 will save the copy of X and b into their working memory, thread 2 executes, because of his copy b=false, so will go into the infinite loop, Thread 1 was modified after it was b=true in its own copy, but thread 2 was not immediately aware, so executing the above code does not print "execution end" because thread 2 is always executing.
Runs out of the running state and does not print end of execution
If we decorate B with the volatile keyword, the miracle will appear.
Note: Here the B is volatile modified
volatile Boolean b = false;
This is how the process will be
After adding the volatile keyword modifier to B, thread 1 modifies B, and then immediately updates the value in memory, thread 2 discovers that its copy has expired by sniffing, then gets the b=true value from memory, then jumps out of the while loop and finishes. reference materials:
Why volatile cannot guarantee atomicity and atomic can.
Non-blocking synchronization algorithm and a CAS (Compare and Swap) lock-free algorithm
The art of Java concurrent programming