There are two problems to be solved in concurrent programming: implementation of communication between threads and synchronization control between threads. To address these two problems, there are two concurrency models available in imperative programming — the shared memory model and the messaging model. How do these two models solve the two problems of concurrent programming?
-
Shared memory model: threads communicate through shared memory. Synchronization is achieved by displaying a method or piece of code that requires mutually exclusive execution between threads.
-
Message transfer model: threads communicate by sending messages; Synchronization is achieved by controlling the order of execution by sending messages before receiving them.
Java concurrency is a shared memory model.
1. Java Memory Model (JMM)
The Java memory model is part of the JavaTM language specification and includes the following:
1.1 Java Memory Model Controls communication between Java threads by controlling the interaction between main memory and the local memory of each thread
Because the Java concurrency model uses a shared memory model, it implements communication between Java threads by controlling when a shared variable written by one thread is visible to another thread. From an abstract point of view, the Java memory model defines an abstract relationship between threads and main memory: shared variables between threads are stored in main memory, and each thread has a private Local memory where it stores copies of shared variables to read/write. One thread A reads and writes to the local memory copy of the shared variable and then writes to the main memory, while another thread B reads the new value of the shared variable from the main memory.
Local memory is an abstraction of the JMM and does not really exist. It covers caching, write buffers, registers, and other hardware and compiler optimizations.
1.2 The Java Memory Model disallows specific types of compiler reordering and prevents processor instruction reordering by inserting specific types of memory barriers in the Java compiler during instruction sequence generation
In order to increase CPU utilization when executing a program, compilers and processors often multiorder instructions. Reordering can be divided into the following three categories:
1) Compiler optimized reordering: The compiler can rearrange the execution order of statements without changing the semantics of a single-threaded program. 2) Sequential reordering of instruction sets: Modern processors use instruction-level parallelism to execute multiple instructions on top of each other. If the data is not dependent, the processor can change the execution order of the machine instructions corresponding to the statement. 3) Reordering of the memory system: Since the processor uses caching and read/write buffers, loading and storage operations can appear to be out of order.
These reorders follow the as-if-serial principle, that is, no matter how the reordering is done, the program execution result in single-thread condition will not be changed, but the program execution result in multi-thread condition cannot be guaranteed. In order to ensure the results of multithreaded program execution, the Java memory model forbids certain types of compiler reordering and prevents processor instruction reordering by inserting certain types of memory barriers during the generation of instruction sequences by the Java compiler.
1.2.1 happens-before rules
The Java memory model disallows a certain type of compiler reorder by generalizing to a happens-before rule that reads as follows:
- Procedural order rule: every action in a thread Happens-before any subsequent action in the thread.
- Monitor lock rule: a lock is unlocked, happens-before a lock is subsequently locked.
- Volatile variable rule: Writes to a volatile field, happens-before any subsequent reads to that volatile field.
- Transitivity: If A happens-before B, and B happens-before C, then A happens-before C.
Looking at happens-before rules here is a bit of nonsense, but these rules are for the compiler, and we know them just to see how the Java memory model constrons compiler reordering.
1.3 Java memory model ensures memory consistency for properly synchronized multithreaded programs
The Java memory model guarantees that if the program is properly synchronized, its execution will be sequentially consistent — that is, the execution of the program will be the same as the execution of the program in the sequentially consistent memory model. To put it bluntly, the Java memory model guarantees execution results when the programmer uses synchronization primitives (synchronized, volatile, and final) correctly. Later sections will look at each of these synchronization primitives in order to understand how they ensure memory consistency.
3. Summary
This chapter focuses on the core content of Java concurrency model -Java memory model. For programmers, writing usable concurrent programming requires only proper use of the concurrency keywords provided by Java, and no knowledge of the contents of the Java memory model, which is used by JVM manufacturers to regulate computers and compilers. We now understand this knowledge in order to systematically understand Java concurrency and understand its principles and implementation. Simply put, the Java Memory model is the guiding specification for the Java language to solve concurrent programming problems.
References: The Art of Concurrent Programming in Java