Series Chapters
sequence
volatile
The keyword is used when the data in the JMM (Java Memory Model) working memory fails to be refreshed to the main memory in time during multi-threaded processing, resulting in abnormal execution of other threads.
loop that can't be exited
private static boolean RUN = true;
// 线程t1中的`while`根据条件变量`RUN`来结束循环
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (RUN) {
// ...
}
}, "t1");
t.start();
sleep(1);
// 线程t1不会如预想的停下来
RUN = false;
}
复制代码
After running the above code, we expected that thread t1 would exit the loop after the condition variable was changed, but it did not. So let's analyze it.
- The value read by the initial
t1
thread is sentRUN
to the working memory. Because the while loop needs to readRUN
the value frequently, itJIT
will be optimized andRUN
cached in thet1
working memory of the thread. Main
The thread changes theRUN
value and synchronizes it to the main memory, but thet1
thread still reads the value of the working memory, which eventually causes its loop to fail to exit
volatile solution
volatile
Can be used to decorate member variables and static member variables, it makes the thread must go to the main memory to get the value
// 添加`valatile`关键字
private static volatile boolean RUN = true;
// 这时程序可以正常结束
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (RUN) {
// ...
}
}, "t1");
t.start();
sleep(1);
RUN = false;
}
复制代码
visibility
The above example reflects the volatile
visibility, which ensures that among multiple threads, volatile
the modification of a variable by one thread is visible to another thread.
Visibility and Atomicity
Atomicity is to ensure that instruction interleaving will not occur in the execution of a certain code block in multiple threads, but visibility can only ensure that the latest value is obtained, and cannot prevent instruction interleaving.
synchronized
The atomicity of the code block can be guaranteed, and the visibility of variables within the code block can also be guaranteed. But it is a heavyweight operation with relatively low performance.
orderliness
Ordering involves reordering, let's first understand reordering.
Why reorder?
Simple to understand, but very complex at the in-depth level. In-depth see why instruction reordering is required
System level: The CPU accesses the value during calculation. If the existing value in the register is often used, there is no need to go to the memory to read it.
Java level:
public static void main(String[] args) {
String a = "a" + "b";// 1
String b = "ab"; // 2
System.out.println(a == b);
}
复制代码
After the above code is optimized by the compiler, it is actually a ab
string in the string constant pool. If the reordering optimization is 2
executed first , and the string is obtained first ab
, then the two strings 1
will not be created .a
b
Problems caused by reordering
In a single-threaded environment, reordering is not a problem; but in a multithreaded environment, reordering can lead to unexpected results.
// 计数
static int i = 1;
// 定义四个静态变量
private static int x, y, a, b = 0;
public static void main(String[] args) throws InterruptedException {
while (true) {
Thread t1 = new Thread(() -> {
a = 1; // 1
x = b; // 2
});
Thread t2 = new Thread(() -> {
b = 1; // 3
y = a; // 4
});
t1.start();
t2.start();
t1.join();
t2.join();
// 打印输出
String result = "第" + i++ + "次执行x=" + x + ", y=" + y;
System.out.println(result);
if (x == 0 && y == 0) {
break;
}
// 修改完后重新赋值
x = 0;
y = 0;
a = 0;
b = 0;
}
}
复制代码
As shown in the above code, after running, we can see that there are several output situations expected:
- Synchronous execution, result x=0, y=1
- Instructions are interleaved, result x=1, y=1 or x=1, y=0
But there will actually be x=0, y=0, as shown in the figure below (the execution time may be longer)
This is the result of instruction reordering, as shown in the figure:
2
Before 1
execution, 4
before 3
execution.
volatile principle
volatile
The underlying implementation is the memory barrier mechanism, as follows:
Barrier Type | Instructions | |
---|---|---|
LoadLoad | Load1; LoadLoad; Load2; | Make sure that Load1 reads data before Load2 and all subsequent reads |
StoreStore | Store1; StoreStore; Store2; | Make sure Store1 writes data to main memory, and before Store2 and subsequent writes |
LoadStore | Load1; LoadStore; Store2; | Make sure that Load1 reads data before Store2 and all subsequent writes |
StoreLoad | Store1; StoreLoad; Load2; | Make sure that Store1 is flushed to main memory when writing data, and before Load2 and subsequent reads |
The following is the code snippet for processing and instruction in the file under the openjdk8
root path , in which a barrier is added after the variable is written ./hotspot/src/share/vm/interpreter
bytecodeInterpreter.cpp
putstatic
putfield
volatile
StoreLoad