Java memory Model
Java Virtual Machines The view in the specification defines a Java memory model (MODEL,JMM) that masks the access differences of various hardware and operating systems to enable Java programs to achieve consistent memory access across various platforms.
main memory vs. working memory
The main goal of the Java memory model is to define access rules for variables in the program, where the variables (Variables) Include instance fields, static fields, and elements that make up the array object, but do not include local variables and method parameters, because the latter is thread-private.
The Java memory model specifies that all variables are stored in the main memory. Each thread has its own working memory (working memories), and the thread's working memory holds a copy of the Master memory copies of the variables used by the thread, and all operations of the thread on the variable (read, assign, and so on) must be made in working memory and not directly read and write to the variables in main memory. There is no direct access to variables in the other's working memory between different threads, and the transfer of variable values between threads needs to be done through main memory, and the interaction between threads, main memory, and working memory is as follows:
Inter-memory interaction operations
For implementation details about how a variable is copied from main memory to working memory, how to synchronize from the working memory to main memory, the following 8 operations are defined in the Java memory model, and the virtual machine implementation must ensure that each of the operations mentioned below is atomic and non-divided.
- Lock: Acts on the main memory variable, which identifies a variable as a thread-exclusive state.
- Unlock (Unlocked): Acts on the main memory variable, which releases a variable that is locked and the released variable can be locked by another thread.
- READ: Acts on the main memory variable, which transfers the value of a variable from main memory to the working memory of the thread for subsequent load actions.
- Load (load): Acts on a working memory variable, which places the value of a read operation from the main memory into a variable copy of the working memory.
- Use: Acts on a working memory variable, which passes the value of a variable in the working memory to the execution engine, which is performed whenever the virtual opportunity is to a bytecode instruction that needs to use the value of the variable.
- Assign (Assignment): acts on the working memory variable, which assigns a value from the execution engine to the working memory variable, which is performed whenever the virtual opportunity is to a byte-code instruction that assigns a value to a variable.
- Store: A variable acting on a working memory that transfers the value of a variable in the working memory to the main memory for use by the write operation.
- Write: Acts on the main memory variable, which puts the value of the variable that the store operation obtains from the working memory into a variable of main memory.
If you want to copy a variable from main memory to working memory, perform the read and load operations sequentially, and if you want to synchronize the variables from the working memory back to the main memory, execute the store and write operations sequentially. Both sets of operations need to be executed sequentially without having to be contiguous. In addition, the Java memory model also stipulates that the following rules must be met when performing the 8 basic operations described above:
- Does not allow you to read with one of the load, store, and write operations, which means that a variable is not allowed to read from the main memory but the working memory is not accepted, or if it is initiated from the working memory but not accepted by the main memory.
- A thread is not allowed to discard its assign operation, that is, the variable must be synchronized back to main memory after it has changed in working memory.
- A thread is not allowed to synchronize data from the working memory of the thread to main memory for no reason (no assign has occurred).
- A new variable can only be born in main memory and not allow direct use of a variable that is not initialized (load or assign) in working memory, in other words, the Assign and load operations must be performed before a variable is implemented using, store.
- A variable is allowed only one thread to lock it at the same time, a single lock operation can be repeated multiple times by the same thread, and after the lock is executed multiple times, the variable is unlocked only by performing the same number of unlock operations.
- If you perform a lock operation on a variable, that will empty the value of this variable in the working memory, and you need to re-execute the value of the load or assign operation initialization variable before the execution engine uses the variable.
- If a variable is not locked by the lock operation beforehand, 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 performing a unlock operation on a variable, you must first synchronize this variable back into main memory (just the Sotore, write operation).
volatile keyword
Keyword volatile is the most lightweight synchronization mechanism provided by a Java virtual machine, and understanding the semantics of volatile variables is useful for understanding other features of multithreaded operations later on. First, let's look at the role of this keyword.
When a variable is defined as volatile, it will have two properties, the first of which is to ensure that the variable is visible to all threads, where "visibility" means that when a thread modifies the value of the variable, the new value is immediately known to other threads. However, because the operations in Java are not atomic operations, the operation of volatile variables is not completely secure under concurrency conditions. The following is the sample code:
PackageCom.overridere.twelve;/** * volatile variable self-increment operation Test */ Public class volatiletest { Public Static volatile intRace =0; Public Static void Increase() {race++; }Private Static Final intThreads_count = -; Public Static void Main(string[] args) {thread[] threads =NewThread[threads_count]; for(inti =0; i < Threads_count; i++) {Threads[i] =NewThread (NewRunnable () {@Override Public void Run() { for(inti =0; I <10000; i++) {increase (); } } }); Threads[i].start (); }//wait for all cumulative threads to end while(Thread.activecount () >1) Thread.yield (); System.out.println (race); }}
This code initiated 20 threads, each thread to the variable race 10,000 self-increment operation, if race is thread-safe, the final output should be 200000, but the real result is a value less than 200000, which is asked why?
Public Static void Increase(); Descriptor: () V flags:acc_public, acc_static code:stack=2, locals=0, args_size=0 0: Getstatic # - //Field race:i 3: iconst_14: Iadd5: Putstatic # - //Field race:i 8:returnLinenumbertable:lineTen:0Line One:8Localvariabletable:start Length Slot Name Signature
The problem is in "race++", with the JAVAP command to get the above bytecode command, found that only one line of code increase () method in the class file is composed of 4 bytecode instructions, From this analysis, it is easy to analyze the reason for the unsafe thread: When the getstatic instruction takes the value of race to the top of the operand stack, the volatile keyword guarantees that the value of race is correct at this time, but in the execution of iconst_1, iadd these instructions, Other threads may have increased the race, and the value of the top of the operation Stack has become outdated data. This means that, in this case, the volatile keyword only guarantees that the getstatic instruction from Chang to the top of the operand stack is thread-safe (from main memory to working memory), the scope is this, and then it loses its function, and the subsequent increase is not thread-safe.
- The result of the operation does not depend on the current value of the variable, or can guarantee that only a single thread modifies the value of the variable.
- A variable does not need to participate in the invariant constraint with its other variables.
The first sentence means: No matter what I used to be, it doesn't affect me now.
The second sentence means: No matter what others are like, as long as I have nothing to do, it will not affect me.
As the following example code:
volatileboolean isVolatile;publicvoidshutdown(){ true;}publicvoiddoWork(){ while(!isVolatile){ //do stuff }}
The Isvolatile variable in the above code, no matter what it was, does not interfere with the current thread assignment, nor is it tied together with other variables to participate in the invariant constraint.
The second semantics of using volatile variables is to prohibit command reordering optimizations, and ordinary variables only guarantee that the correct results will be obtained in all areas that depend on the results of the execution of the method, without guaranteeing that the order of the variable assignment is consistent with the order of execution in the program code. Because this is not perceptible during the execution of a thread's methods, the so-called "line range is expressed as serial semantics" (Within-thread as-if-serial Semantics) described in the Java memory model.
Order reordering means that instructions are not executed in the order specified by the program, but are not meant to be arbitrarily re-ordered. For example, instruction 1 adds 10 to the value in address a, instruction 2 multiplies the value in address a by 2, and instruction 3 subtracts the value in address B by 3, when instruction 1 and instruction 2 are dependent, the order between them cannot be re-ordered-(A+10) * * is not equal to a*2+10, but instruction 3 can be re-ordered to instruction 1, 2 before or in the middle.
And the Valotile keyword is how to implement the Prohibition command reordering it?
A variable with the volatile keyword modifier, the assignment will take more than one operation, which will synchronize the changes to memory, meaning that all previous operations have been completed, this operation is equivalent to memory Barrier, In this way, the command reordering will not be able to reorder the subsequent instructions to the location before the memory barrier. Memory barriers are not required when only one CPU accesses memory.
atomicity, visibility and ordering
atomicity (atomicity): atomic variable operations that are directly guaranteed by the Java memory model include read, load, assign, use, and write.
Visibility (Visibility): visibility means that when a thread modifies the value of a shared variable, other threads can immediately know the change. The Java memory model realizes visibility by synchronizing the new value back to main memory after the variable is modified, and by refreshing the variable value from the main memory before the variable is read, which relies on the main memory as the delivery medium. The difference between a volatile variable and a normal variable is that the special rules for volatile ensure that the new value is immediately synchronized to main memory and flushed from the main memory immediately before each use.
order (Ordering): All operations are ordered if viewed within this thread, and all operations are unordered if another thread is observed in one thread. The first half of the sentence refers to the "line range expression as a serial semantics", the latter sentence refers to the "order reordering" phenomenon and "working memory and main memory synchronization delay" phenomenon.
current principles of occurrence
The first occurrence is a partial-order relationship between two operations defined in the Java memory model, if operation a precedes action B, meaning that the effect of operation A can be observed by Operation B before Operation B occurs. Take a look at the following example:
//以下操作在线程A中执行1;//以下操作在线程B中执行j = i;//以下操作在线程C中执行2;
Assuming that the action in thread a precedes the operation of thread B, name can determine that the value of the variable J must be equal to I after the operation of Line B, based on two: according to the current occurrence principle, the result of "i=1" can be observed by thread B, and the second is that thread C has not been executed. No other thread modifies the value of the variable I after the end of the thread a operation.
Change the question again, we still maintain the antecedent of thread A and thread B, and thread C appears between threads A and thread B, but thread C and thread B do not have an antecedent relationship, so what is the value of J? The answer is not sure, because thread C and thread B are not sure which thread performed the modification to the variable J first.
Java Memory models and threads