In Java thread concurrent processing, there is currently a lot of confusion about the use of the keyword volatile. It is thought that using this keyword, everything will be fine when performing multi-threaded concurrent processing.
The Java language supports multi-threading. In order to solve the problem of thread concurrency, the synchronized block and volatile keyword mechanisms are introduced inside the language.
synchronized
Everyone is familiar with synchronized blocks, which are implemented through the synchronized keyword. With the addition of synchronized and block statements, when accessed by multiple threads, only one thread can use synchronized modified methods or code blocks at the same time.
volatile
For variables modified with volatile, every time the thread uses the variable, it will read the modified value of the variable. Volatile can easily be misused to perform atomic operations.
Let's look at an example below. We implement a counter. Every time the thread starts, the counter inc method will be called to add one to the counter.
Execution environment - jdk version: jdk1.6.0_31, memory: 3G cpu: x86 2.4G
Copy the code code as follows:
public class Counter {
public static int count = 0;
public static void inc() {
//Delay 1 millisecond here to make the result obvious
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
count++;
}
public static void main(String[] args) {
//Start 1000 threads at the same time to perform i++ calculations and see the actual results
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
}
}).start();
}
//The value here may be different each time it is run, possibly 1000
System.out.println("Run result: Counter.count=" + Counter.count);
}
}
Running result: Counter.count=995
The actual operation result may be different every time. The result of this machine is: running result: Counter.count=995. It can be seen that in a multi-threaded environment, Counter.count does not expect the result to be 1000.
Many people think that this is a multi-thread concurrency problem. You only need to add volatile before the variable count to avoid this problem. Then we will modify the code to see if the result meets our expectations.
Copy the code code as follows:
public class Counter {
public volatile static int count = 0;
public static void inc() {
//Delay 1 millisecond here to make the result obvious
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
count++;
}
public static void main(String[] args) {
//Start 1000 threads at the same time to perform i++ calculations and see the actual results
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Counter.inc();
}
}).start();
}
//The value here may be different each time it is run, possibly 1000
System.out.println("Run result: Counter.count=" + Counter.count);
}
}
Running result: Counter.count=992
The running result is still not 1000 as we expected. Let’s analyze the reasons below.
In the article Java Garbage Collection, the allocation of memory during JVM runtime is described. One of the memory areas is the jvm virtual machine stack. Each thread has a thread stack when it is running. The thread stack stores variable value information when the thread is running. When a thread accesses the value of an object, it first finds the value of the variable corresponding to the heap memory through the object reference, and then loads the specific value of the heap memory variable into the thread local memory to create a copy of the variable. After that, the thread does not It has nothing to do with the object's variable value in the heap memory. Instead, it directly modifies the value of the copy variable. At a certain moment after the modification (before the thread exits), the value of the thread variable copy is automatically written back to the object's variable in the heap. . In this way, the value of the object in the heap changes. The picture below
Describe this interaction
read and load copies variables from main memory to current working memory
use and assign execute code and change shared variable values
store and write refresh main memory related content with working memory data
where use and assign can appear multiple times
However, these operations are not atomic. That is, after read load, if the main memory count variable is modified, the value in the thread working memory will not change because it has been loaded, so the calculated result will be as expected. no the same
For volatile-modified variables, the JVM virtual machine only ensures that the value loaded from the main memory to the thread working memory is the latest
For example, if thread 1 and thread 2 find that the count value in the main memory is both 5 during read and load operations, then they will load the latest value.
After thread 1 heap count is modified, it will be written to the main memory, and the count variable in the main memory will become 6.
Since thread 2 has already performed read and load operations, after performing the operation, it will also update the main memory count variable value to 6.
As a result, after two threads are modified in time using the volatile keyword, there will still be concurrency.