In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)06/01 Report--
This article introduces the relevant knowledge of "what is the Java memory model?". In the operation of actual cases, many people will encounter such a dilemma. Next, let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Java memory model
The JVM memory mode we often talk about refers to the memory partition of JVM. While the Java memory mode is a virtual machine specification, there is no real Java memory model (Java Memory Model,JMM) defined in the Java virtual machine specification, which is used to shield the memory access differences of various hardware and operating systems, so as to achieve consistent concurrency of Java programs on various platforms. JMM regulates how Java virtual machines and computer memory work together: how and when a thread can see the values of shared variables modified by other threads, and how to access shared variables synchronously if necessary.
The original Java memory model had some shortcomings, so the Java memory model was revised during Java1.5. This version of the Java memory model is still in use in Java8.
Java memory model (not just JVM memory partitioning): the call stack and local variables are stored on the online stack, and objects are stored on the heap.
Schematic diagram of content model
Hardware point of view diagram
A local variable may be a primitive type, in which case it always "stays" on the thread stack.
A local variable may also be a reference to an object. In this case, the reference (the local variable) is stored on the thread stack, but the object itself is stored on the heap.
An object may contain methods, which may contain local variables. These local variables are still stored on the online stack, even if the objects to which these methods belong are stored on the heap
The member variables of an object may be stored on the heap with the object itself. Whether the member variable is a primitive type or a reference type.
Static member variables are also stored on the heap along with the class definition.
The object stored on the heap can be accessed by all threads that hold a reference to the object. When a thread can access an object, it can also access the object's member variables. If two threads call the same method on the same object at the same time, they will both access the member variable of the object, but each thread has a private copy of the member variable.
Hardware memory architecture
The modern hardware memory model is somewhat different from the Java memory model, and it is also important to understand the architecture of the memory model and how the Java memory model works with it.
A simple illustration of the hardware architecture of modern computers:
Multiple CPU: a modern computer usually consists of two or more CPU. Some of these CPU are multicore. From this, it is possible to run multiple threads at the same time on a modern computer with two or more CPU. There is no problem for each CPU to run a thread at some point. This means that if your Java program is multithreaded, one thread on each CPU in your Java program may execute concurrently.
CPU registers: each CPU contains a series of registers that form the basis of memory in the CPU. CPU performs operations on registers much faster than on main memory. This is because CPU accesses registers much faster than main memory.
Cache cache: because there is a gap of several orders of magnitude between the computing speed of the computer's storage device and the processor, modern computer systems have to add a cache (Cache) whose read and write speed is as close as possible to the processor's speed as a buffer between memory and processor: copy the data needed for the operation into the cache, so that the operation can be performed quickly. When the operation is finished, it is synchronized back to memory from the cache so that the processor does not have to wait for slow memory reads and writes. CPU accesses the cache layer faster than the main memory, but usually a little slower than the internal registers. Each CPU may have a CPU cache layer, and some CPU may have multiple caches. At some point, one or more cache lines (cache lines) may be read to the cache, and one or more cache lines may be flushed back to the main memory.
Memory: a computer also contains a main memory. All CPU have access to main memory. Main memory is usually much larger than the cache in CPU.
How it works: normally, when a CPU needs to read main memory, it will read part of main memory into the CPU cache. It may even read part of the cache into its internal register and then perform the operation in the register. When CPU needs to write the result back to main memory, it refreshes the value of the internal register to the cache and then flushes the value back to main memory at some point in time.
Some problems: (especially in multithreaded environment)
Cache consistency issues: in multiprocessor systems, each processor has its own cache, and they share the same main memory (MainMemory). Cache-based storage interaction solves the speed contradiction between processor and memory, but it also introduces a new problem: cache consistency (CacheCoherence). When the computing tasks of multiple processors involve the same main memory area, it may lead to inconsistency of their respective cached data. If this happens, whose cached data will prevail when synchronizing back to main memory? In order to solve the problem of consistency, each processor needs to follow some protocols when accessing the cache, and operate according to protocols when reading and writing, such as MSI, MESI (IllinoisProtocol), MOSI, Synapse, Firefly and DragonProtocol, and so on:
Instruction reordering problem: in order to make full use of the operation units within the processor, the processor may optimize the input code with out-of-order execution (Out-Of-Order Execution). After calculation, the processor will reorganize the results of out-of-order execution to ensure that the results are consistent with the results of sequential execution, but it does not guarantee that the order of each statement in the program is consistent with the order in the input code. Therefore, if there is a computing task that relies on the intermediate results of another computing task, its ordering cannot be guaranteed by the order of the code. Similar to processor out-of-order execution optimization, there is a similar instruction reordering (Instruction Reorder) optimization in the just-in-time compiler of the Java virtual machine.
Bridging between Java memory model and hardware memory architecture
There are differences between the Java memory model and the hardware memory architecture. The hardware memory architecture does not distinguish between the thread stack and the heap. For hardware, all thread stacks and heaps are distributed in main memory. Part of the thread stack and heap may sometimes appear in the CPU cache and in registers within the CPU. As shown in the following figure:
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 thread has a private local memory (Local Memory), an abstract concept of JMM that doesn't really exist. It covers caching, write buffers, registers, and other hardware and compiler optimizations. The thread is stored in local memory to read / write a copy of the shared variable.
At a lower level, the main memory is the memory of the hardware, and in order to obtain better running speed, virtual machines and hardware systems may give priority to storing working memory in registers and caches.
The working memory (working memory) of threads in the Java memory model is an abstract description of the registers and caches of cpu. JVM's static internal storage model (JVM memory model) is only a physical partition of memory, it is limited to memory, and only limited to JVM memory.
Inter-thread communication under the JMM model:
Communication between threads must pass through main memory. As follows, if you want to communicate between thread An and thread B, you must go through the following two steps:
1) Thread A flushes the updated shared variables in local memory A to main memory.
2) Thread B goes to main memory to read the shared variables that have been updated by Thread A.
With regard to the specific interaction protocol between main memory and working memory, that is, the implementation details of how a variable is copied from main memory to working memory, and how to synchronize from working memory to main memory, the Java memory model defines the following eight operations:
Lock (locking): a variable that acts on main memory and identifies a variable as a thread exclusive state.
Unlock (unlock): acts on the main memory variable to release a variable in the locked state so that the released variable can be locked by other threads.
Read (read): acts on the main memory variable to transfer the value of a variable from the main memory to the thread's working memory for subsequent load actions to use
Load (load): a variable that acts on working memory that puts the value of the variable obtained by the read operation from main memory into a copy of the variable in working memory.
Use (use): a variable that acts on working memory and passes a variable value in working memory to the execution engine, which will be performed whenever the virtual machine encounters a bytecode instruction that requires the value of the variable.
Assign (assignment): a variable that acts on working memory that assigns a value received from the execution engine to the variable in working memory, which is performed whenever the virtual machine encounters a bytecode instruction to assign the variable.
Store (storage): a variable that acts on working memory and transfers the value of a variable in working memory to main memory for subsequent write operations.
Write (write): a variable that acts on main memory and transfers store operations from the value of a variable in working memory to a variable in main memory.
The Java memory model also states that the following rules must be met when performing the above eight basic operations:
If you want to copy a variable from main memory to working memory, you need to perform read and load operations sequentially, and if you synchronize variables from working memory back to main memory, you need to perform store and write operations sequentially. However, the Java memory model only requires that the above operations must be performed sequentially, but there is no guarantee that they must be performed continuously.
One of the read and load, store and write operations is not allowed to appear alone
A thread is not allowed to discard its most recent assign operation, that is, variables must be synchronized to main memory after being changed in working memory.
A thread is not allowed to synchronize data from working memory back to main memory for no reason (no assign operation has occurred).
A new variable can only be born in main memory, and it is not allowed to use an uninitialized variable (load or assign) directly in working memory. That is, assign and load operations must be performed before use and store operations are performed on a variable.
A variable allows only one thread to perform lock operations on it at a time, but the lock operation can be executed repeatedly by the same thread. After executing lock many times, the variable will be unlocked only if the unlock operation is performed the same number of times. That is, lock and unlock must appear in pairs!
If a lock operation is performed on a variable, the value of the variable will be emptied in working memory, and the value of the variable will need to be initialized by a load or assign operation before the execution engine can use it
If a variable is not locked by a lock operation in advance, it is not allowed to perform a unlock operation on it, nor is it allowed to unlock a variable that is locked by another thread.
Before you can unlock a variable, you must first synchronize the variable to main memory (perform store and write operations).
Problems solved by Java memory model
Some specific problems may arise when objects and variables are stored in different memory areas of the computer. The establishment of Java memory model revolves around: how to deal with multi-thread read synchronization and visibility (multi-thread cache and instruction reordering), multi-thread write synchronization and atomicity (multi-thread competition for race condition) in the process of multithread concurrency.
1. Synchronization and visibility of multithreaded reads
Visibility (visibility of shared objects): visibility of thread modifications to shared variables. When one thread modifies the value of a shared variable, other threads are immediately aware of the modification
2. Visibility problems caused by thread caching:
If two or more threads share an object without using volatile declaration or synchronization correctly, one thread updating the shared object may not be visible to other threads: the shared object is initialized in main memory. A thread running on CPU reads the shared object into the CPU cache and then modifies the object. As long as the CPU cache is not flushed, the modified version of the object is not visible to threads running on other CPU. This approach may result in each thread having a private copy of the shared object, each staying in a different CPU cache.
The following figure illustrates this situation. The thread running on the left CPU copies the shared object into its CPU cache and changes the value of the count variable to 2. This modification is not visible to other threads running on the CPU on the right, because the value of the modified count has not been flushed back to main memory.
To solve this memory visibility problem, you can use:
Volatile keyword in Java: the volatile keyword guarantees that a variable is read directly from main memory, and if the variable is modified, it will always be written back to main memory. The Java memory model realizes visibility by synchronizing the new value back to the main memory after the variable is modified, and refreshing the variable value from the main memory before the variable is read, which depends on the main memory as the transmission medium, whether it is an ordinary variable or a volatile variable. The difference between ordinary variables and volatile variables is that the special rules of volatile ensure that the new values can be synchronized to the main memory immediately. And each thread is refreshed from main memory immediately before each use of the volatile variable. So we can say that volatile ensures the visibility of variables in multithreaded operations, while ordinary variables do not, although it will eventually be synchronized to main memory, there may be old "dirty data" used by other threads at this time.
Synchronized keyword in Java: the visibility of the synchronized block is obtained by two rules: "if you lock a variable, the value of this variable will be cleared in working memory, and the value of the variable needs to be initialized by load or assign operations before the execution engine uses this variable" and "before performing unlock operations on a variable, you must synchronize the variable back to main memory (perform store and write operations).
Final keyword in Java: the visibility of the final keyword means that once the field modified by final is initialized in the constructor and the constructor does not pass the reference of "this" (this reference escape is a very dangerous thing, other threads may access the "initialized half" object through this reference), then the value of the final field can be seen in other threads (without synchronization)
3. Visibility problems caused by reordering:
The natural ordering in Java programs can be summed up in one sentence: if you look within a local thread, all operations are orderly ("serial within a thread" (Within-Thread As-If-Serial Semantics)); if you observe another thread in one thread, all operations are out of order ("instruction reordering" and "thread working memory synchronization delay" phenomenon).
The Java language provides two keywords, volatile and synchronized, to ensure orderly operations between threads:
The volatile keyword itself contains the semantics of prohibiting instruction reordering.
Synchronized is obtained by the rule that only one thread is allowed to lock a variable at a time, which determines that two synchronous blocks holding the same lock can only enter serially.
Reordering of instruction sequences:
1) compiler-optimized reordering. The compiler can rearrange the execution order of statements without changing the semantics of single-threaded programs.
2) instruction-level parallel reordering. Modern processors use instruction-level parallelism (Instruction-LevelParallelism,ILP) to execute multiple instructions in overlap. If there is no data dependency, the processor can change the execution order of the statements corresponding to the machine instructions.
3) reordering of memory systems. Because the processor uses caching and read / write buffers, it appears that load and storage operations may be performed out of order.
The write buffer on each processor is visible only to the processor on which it is located. As a result, the order in which the processor performs memory operations may be inconsistent with the actual order in which memory operations are performed. Because modern processors use write buffers, modern processors allow write-read operations to be reordered:
Data dependency: when reordering, the compiler and processor will abide by the data dependency, and the compiler and processor will not change the execution order of the two operations that have data dependency. (the data dependencies mentioned here are only for instruction sequences executed in a single processor and operations performed in a single thread, and data dependencies between different processors and threads are not considered by compilers and processors.)
The effect of instruction reordering on memory visibility
When there is no data dependency between 1 and 2, 1 and 2 may be reordered (similar to 3 and 4). The result is that when thread B executes 4, it may not be possible to see the changes made by writer thread A to the shared variable during execution 1.
Example of instruction reordering that changes the execution result of a multithreaded program:
The flag variable is a tag that identifies whether the variable a has been written. Let's assume that there are two threads, An and B, that first execute the writer () method, and then thread B then executes the reader () method. When thread B executes operation 4, can you see thread A writing to the shared variable an in operation 1?
The answer is: not necessarily.
Since there is no data dependency between operation 1 and operation 2, the compiler and processor can reorder the two operations; similarly, operation 3 and operation 4 have no data dependency, and the compiler and processor can reorder the two operations.
As-if-serial semantics:
No matter how much reordering (compiler and processor to improve parallelism), the execution result of the (single-threaded) program cannot be changed. (compilers, runtime, and processors must all follow as-if-serial semantics)
Happens before:
Starting with JDK 5, Java uses the new JSR-133 memory model, and JSR-133 uses the concept of happens-before to illustrate memory visibility between operations: in JMM, if the result of one operation needs to be visible to another operation (two operations can be either within one thread or between different threads), then there must be a happens-before relationship between the two operations:
Program order rules: each operation in a thread, happens-before any subsequent operations in that thread.
Monitor lock rule: unlock a lock, which is subsequently locked by happens-before.
Volatile variable rule: write to a volatile domain, happens-before any subsequent reads to that volatile domain.
Transitivity: if A happens-before B and B happens-before C, then A happens-before C.
A happens-before rule corresponds to one or more compiler and processor reordering rules
The memory barrier prevents certain types of processors from reordering:
Reordering may cause memory visibility problems in multithreaded programs. For processor reordering, JMM's processor reordering rules require the Java compiler to insert specific types of memory barrier (Memory Barriers,Intel called Memory Fence) instructions when generating instruction sequences, and use memory barrier instructions to prohibit specific types of processor reordering. Provide programmers with consistent memory visibility guarantees by prohibiting specific types of compiler reordering and processor reordering.
To ensure memory visibility, the Java compiler inserts memory barrier instructions where the instruction sequence is generated to prevent certain types of processors from reordering.
StoreLoad Barriers is an "all-powerful" barrier, which has the effect of other three barriers at the same time. Most modern multiprocessors support this barrier (other types of barriers are not necessarily supported by all processors). Performing this barrier can be expensive because the current processor usually flushes all the data in the write buffer into memory (Buffer Fully Flush).
Multithreaded write synchronization and atomicity
Multithreaded contention (Race Conditions) problem: race conditions occurs when reading, writing, and checking shared variables.
Race conditions can occur if two or more threads share an object and multiple threads update variables on the shared object.
Imagine if thread A reads a variable count of a shared object into its CPU cache. Imagine thread B doing the same thing, but in a different CPU cache. Now thread An adds count by 1, and thread B does the same thing. Now count has been added twice, once in each CPU cache. If these increments are performed sequentially, the variable count should be incremented twice, and then the original value + 2 is written back to main memory. However, both additions are executed concurrently without proper synchronization. Whether thread An or thread B writes the modified version of count back to main memory, the modified value will only be increased by 1 by the original value, although it has been increased twice:
Java synchronization blocks can be used to solve this problem. A synchronization block ensures that only one thread can enter the critical section of the code at a time. The synchronous block also ensures that all accessed variables in the code block will be read from main memory, and when the thread exits the synchronous code block, all updated variables will be flushed back to main memory, regardless of whether the variable is declared as volatile or not.
Use atomicity to ensure multithreaded write synchronization:
Atomicity: an operation is performed in an atomic way. Either the operation is not performed, or it is performed atomically, that is, it is not interrupted by other threads during execution.
Achieve atomicity:
The atomic variable operations directly guaranteed by the Java memory model include read, load, assign, use, store, write. We can roughly assume that the access, read and write of basic data type variables, reference type variables, and any type variables declared as volatile are atomic (long and double's non-atomic protocol: for 64-bit data, such as long and double The Java memory model specification allows the virtual machine to divide the read and write operations of 64-bit data that are not modified by volatile into two 32-bit operations, that is, it allows the virtual machine to implement choices that do not guarantee the atomicity of the four operations of load, store, read and write of 64-bit data types, that is, if multiple threads share a variable of type long or double that is not declared as volatile, and read and modify them at the same time Then some threads may read a value that represents "half a variable" that is neither the original value nor the value modified by other threads. However, because almost all the commercial virtual machines on various platforms choose to treat the read and write operation of 64-bit data as atomic operations, it is generally not necessary to declare the long and double variables as volatile when writing code. The reading and writing of these types of variables are naturally atomic, but compound operations such as "basic variable +" / "volatile++" are not atomic.
If the application scenario requires a wider range of atomicity assurance, synchronous block technology is required. The Java memory model provides lock and unlock operations to meet this requirement. The virtual machine provides bytecode instructions monitorenter and monitorexist to implicitly use these two operations, which are reflected in Java code for fast synchronization-the synchronized keyword.
JMM's special rule support for special Java semantics
Volatile summary (ensure memory visibility: instructions for Lock prefixes, memory barriers prohibit reordering)
Synchronized summary (ensuring memory visibility and operational atomicity: mutexes; lock optimization)
That's all for "what does the Java memory model mean?" Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.