Basic concepts of memory model
In concurrent programming, two key issues need to be addressed: how threads communicate with each other and how threads synchronize with each other.
There are two mechanisms for communication between threads: shared memory and message passing.
Shared memory: Threads share the common state of a program, implicitly communicating through the common state of write-read memory.
Messaging: There is no common state between threads, and threads must communicate by sending messages. (Similar to message queues)
Synchronization is the mechanism in a program that controls the relative order in which operations occur between different threads.
There are three important features of concurrent programming
Atomicity – guarantees that instructions are not affected by thread context switches
Visibility – Ensures that instructions are not affected by the CPU cache
Orderability – ensures that instructions are not affected by CPU instruction parallel optimizations
The Java Virtual Machine specification defines a Java Memory Model (JMM) to mask Memory access differences across hardware and operating systems in order to allow Java programs to achieve consistent concurrency across platforms. The main goal of the Java memory model is to define the rules for accessing variables in a program, such as the details of storing variables into and out of memory in the virtual machine.
In order for threads A and B to communicate, they must go through the following two steps:
-
First, thread A refreshes the updated shared variable from local memory A to main memory.
-
Thread B then goes into main memory to read the shared variable that thread A has previously updated.
visibility
An inescapable cycle
/ * * *@Author blackcat
* @version: 1.0
* @description: visibility (volatile can be used to obtain variables from main memory without stopping) * Volatile can be used only with one writer thread and multiple readers *synchronized blocks guarantee both atomicity of the block and visibility of the variables within the block. The downside is that *synchronized is a heavyweight operation with relatively lower performance */
@Slf4j
public class Threadvisibility {
private static boolean flag = true;
public static void main(String[] agrs) {
new Thread(() -> {
log.info("start");
while (flag) {
}
log.info("end");
}, "server").start();
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("Modified flag");
flag = false; }}Copy the code
Volatile can be used to modify both static and member variables. Variables have the following properties
Visibility: a read to a volatile variable will always see the last write to that volatile variable.
Atomicity: reads/writes on any single volatile variable are atomicity, but complex operations such as volatile i++ are not.
Volatile can be used only with one writer thread and multiple readers.
Principle of volatile
The underlying principle behind volatile is Memory barriers.
Write barriers are added to write instructions on volatile variables
Reads on volatile variables are preceded by read barriers
How do I guarantee visibility
A Store Barrier, inserted after a write instruction, causes the latest data written to the cache to be written back to main memory, ensuring that it is immediately visible to other threads
A Load Barrier is inserted before the instruction is read. It invalidates the data in the cache and reloads the data from the main memory to ensure that the latest data is read.
import lombok.extern.slf4j.Slf4j;
/ * * *@Author blackcat
* @create2021/8/1 transgress *@version: 1.0
* @description: * /
@Slf4j
public class VolatileTest {
public static void main(String[] args) {
VolatileExample volatileExample = new VolatileExample();
new Thread(() -> {
volatileExample.writer();
}, "write").start();
new Thread(() -> {
volatileExample.reader();
}, "read").start(); }}@Slf4j
class VolatileExample {
int a = 0;
volatile boolean flag = false;
public VolatileExample(a) {}public void writer(a) {
log.info("writer start");
a = 1;
flag = true;
log.info("writer end");
}
public void reader(a) {
log.info("before{}",a);
if (flag) {
log.info("after{}",a); }}}Copy the code
How to ensure order
The write barrier ensures that when instructions are reordered, code before the write barrier is not placed behind the write barrier.
The read barrier ensures that when instructions are reordered, code after the read barrier is not placed before the read barrier.
order
define
Instruction reordering means that during program execution, the compiler and CPU may reorder instructions for performance reasons.
For example, a processor that supports simultaneous execution of fetch instruction – instruction decoding – execution instruction – memory access – data write back can be called a five-level instruction pipeline. At this time, the CPU can simultaneously run five different stages of instructions (equivalent to a complex instruction with the longest execution time) in a clock cycle, IPC = 1. Essentially, pipeline technology can not shorten the execution time of a single instruction, but it improves the throughput rate of the instruction in a disguised way.
a = b + c;
d = e - f ;
Copy the code
Load B and C first (note that it is possible to load B or C first), but when executing add(b,c), it needs to wait for the loading of B and C to finish before continuing the execution. That is, if a pause is added, the following instructions will also have pauses, which reduces the execution efficiency of the computer.
To reduce this pause, we can load e and f first, and then add(b,c). This has no effect on the program (serial), but reduces the pause. Since add(b,c) needs to stop, it might as well do something meaningful.
In summary, instruction reordering is necessary to improve CPU processing performance. The chaos was a problem, but the sacrifice was worth it.
Instruction reordering guarantees consistency of serial semantics, but there is no obligation to guarantee consistency of semantics across multiple threads.
// reorder (0,0)
public class Disorder {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
while (true) { reSort(); }}static void reSort(a) throws InterruptedException {
Thread one = new Thread(new Runnable() {
public void run(a) {
a = 1; //操作1
x = b; //操作2}}); Thread other =new Thread(new Runnable() {
public void run(a) {
b = 1; //操作3
y = a; //操作4}}); one.start(); other.start(); one.join(); other.join();if (x == 0 && y == 0) {
System.out.println("(" + x + "," + y + ")"); }}}Copy the code
DoubleCheckedLocking problem
/ * * *@Author blackcat
* @version: 1.0
* @description: Double check */
public class DoubleCheckedLocking { / / 1
/ / to join volitale
private static Instance instance; / / 2
/** * instance = new Instance(); * * Set instance to the newly allocated memory address * * reorder the operations on 2 and 3 * * when multiple threads are used, judge instance == null The object is returned directly but the object has not been initialized */
public static Instance getInstance(a) { / / 3
if (instance == null) { //4: First check
synchronized (DoubleCheckedLocking.class) { / / 5: lock
if (instance == null) //6: The second check
instance = new Instance(); //7: Here lies the root of the problem
} / / 8
} / / 9
return instance; / / 10
} / / 11
static class Instance {}}Copy the code
Happen – before the rules
- Rule of program order: Every action in a thread happens before any subsequent action in that thread.
- Monitor lock rule: The unlocking of a lock happens before the subsequent locking of the lock.
- The volatile variable rule: a write to a volatile field happens before any subsequent reads to that field.
- Transitivity: If A happens-before B, and B happens-before C, then A happens-before C.
- Start rule: if thread A performs an action threadb.start () that starts ThreadB, then thread A’s threadb.start () action happens before any action in ThreadB,
- Join rule: if thread A performs the operation threadb.join () and returns successfully, then any operation in ThreadB happens-before thread A returns successfully from the operation threadb.join ().