Classification of concurrent programming models
In concurrent programming, we need to deal with two key issues: how threads communicate with each other and how threads are synchronized (the threads here refer to the activity entities that execute concurrently). Communication refers to the mechanism by which the threads exchange information. In imperative programming, there are two types of communication mechanisms between threads: Shared memory and message delivery.
In the concurrency model of shared memory, threads share the public state of the program, and the threads communicate implicitly through the public state in write-read memory. In the concurrency model of messaging, there is no public state between threads, and threads must communicate explicitly by sending messages explicitly.
Synchronization is the mechanism by which a program controls the relative order of operations between different threads. In the shared-memory concurrency model, synchronization is done explicitly. Programmers must explicitly specify that a method or piece of code requires a mutex between threads to execute. In the concurrency model of messaging, synchronization is implicit because the message must be sent before the message is received.
Java concurrency uses a shared memory model, where communication between Java threads is always implicit and the entire communication process is completely transparent to programmers. If a Java programmer writing a multithreaded program does not understand the working mechanism of implicit communication between threads, it is likely to encounter a variety of strange memory-visibility problems.
Abstraction of the Java memory model
In Java, all instance domains, static domains, and array elements are stored in heap memory, and heap memory is shared between threads (this article uses the term "shared variables" to refer to instance domains, static fields, and array elements). Locals (local variables), the method definition parameters (referred to by the Java language Specification as formal method parameters), and exception processor parameters (exception handler parameters) are not shared between threads. They do not have memory visibility problems and are not affected by the memory model.
Communication between Java threads is controlled by the Java Memory Model (JMM), JMM determines when a thread's write to a shared variable is visible to another thread. From an abstract point of view, JMM defines the abstract relationship between threads and main memory: Shared variables between threads are stored in main memory (main memory), each of which has a private local memory (native memory) that is stored in local memory to read/write copies of shared variables. Local memory is an abstract concept of JMM and is not real. It covers caching, write buffers, registers, and other hardware and compiler optimizations. The abstract diagram of the Java memory model is as follows:
From the figure above, the following 2 steps must be experienced to communicate between thread A and thread B:
First, thread a flushes the shared variable updated in local memory A to main memory.
Thread B then goes to main memory to read shared variables that have been updated before thread A.
The following diagram illustrates these two steps:
As shown in the figure above, local memory A and B have a copy of the shared variable x in main memory. Assuming initially, the X values in these three memory are 0. When thread a executes, it temporarily stores the updated X value (assuming a value of 1) in its own local memory a. When thread A and thread B need to communicate, thread a first flushes the modified X value in its local memory into main memory, at which point the X value in main memory becomes 1. Subsequently, thread B goes to main memory to read the X value after thread A is updated, at which point the X value of thread B's local memory becomes 1.
Overall, the two steps are essentially thread a sending a message to thread B, and the communication process must pass through the main memory. JMM provides memory visibility guarantees for Java programmers by controlling the interaction between primary memory and the local memory of each thread.