A recent look at "in-depth understanding of Java Virtual machines: JVM advanced features and best practices" covers thread-related details about the Java memory model, which is the specification of the JSR 133 definition.
The system looked at the content of the previous chapters of the JSR 133 specification and found it rewarding. Don't talk nonsense, briefly introduce the Java Memory specification.
What is a memory specification
It's so defined in jsr-133.
A memory model describes, given A program and an execution trace of the that program, whether The execution trace is a legal execution of the program. For the Java programming language, the memory model works by examining each of the read in an execution trace and Checki Ng that the write observed by, Read is valid according to certain rules.
This means that a memory model describes whether a given program and its execution path are a legitimate execution path. For the Java preamble, the memory model checks to see if the read operation for a write operation is valid according to a specific rule by examining each read operation in the program execution path.
The Java memory model only defines a specification, and the implementation can be implemented freely according to the actual situation. However, implementations are implemented to meet the specifications of the Java memory model definition.
Processor and memory interaction
Thanks to the development of the silicon industry, the performance of the processor is becoming more and more powerful. There are basically multicore processors on the market today. How to take advantage of the multi-core processor execution program, which makes the program performance greatly improved, is the most important at present.
Currently all operations are performed by the processor, and when we were in college we learned a basic concept program = data + algorithm, then the processor was responsible for the calculation, where did the data get from?
Data can be stored in the processor register (currently x86 processing is based on the Register schema), the processor cache inside, memory, disk, optical drive and so on. The processor accesses this data from fast to slow: register, processor cache, memory, disk, optical drive. To speed up the program, the closer the data is to the processor the better. But registers, processor cache are processor private data, only memory, disk, optical drive is the global data that all processors can access (disk and optical drive We do not discuss here, only to discuss memory) if the program is multithreaded, then different threads may be assigned to different processors to execute, These processors need to load the data from main memory into the processor cache and registers to be executed (as described in the university operating system concept), and after the data is executed, the execution results are synchronized to the main memory. If the data is shared by all threads, a synchronization problem occurs. The processor needs to resolve when the main memory data is synchronized and when the execution results are synchronized to main memory, because the same processor might put the data in the processor cache first so that the program can continue to manipulate the data later. So for the memory data, because of the multiprocessor situation, it will become very complex. Here is an example:
Initial value A = b = 0
Process1 Process2
1:load a 5:load b
2:write a:2 6:add b:1
3:load b 7:load A
4:write b:1 8:write a:1
Assuming that processor 1 loads memory variable A, writes a with a value of 2, then loads B, writes B with a value of 1, handles 2 first, and executes b+1, then the result of B in processor 2 may be 1. Because before load B, it is not known whether processor 1 is already B write will go to main memory. For a, assuming that processor 1 after processor 2 writes A to the main memory, then the value of a is 2.
The memory model is a rule that specifies how the processor synchronizes data with the main memory.
Memory Model Introduction
Before we introduce the Java memory model, let's take a look at two memory models
Sequential consistency Memorymodel: Continuous consistency model. This model defines the order in which the program executes and the order in which the code executes is consistent. That is, if you have two threads, one thread T1 write to the shared variable A, and the other thread T2 a read operation. If the thread T1 is executed before T2 in time, then T2 can see the value after the T1 modification.
This memory model is relatively simple, but also more intuitive, more in line with the real world logic. However, this model is very strict, and it can seriously affect the performance of the program when the multi-processor executes the program concurrently. Since every modification to a shared variable is immediately synchronized with the main memory, the variable cannot be stored inside the processor register or in the processor cache. Cause frequent read and write memory impact performance.
happens-before Memory Model: The first occurrence of models. This model is more difficult to understand. First introduce a current occurrence relationship (happens-before relationship)
If there are two operations A and B exist a Happens-before B, then the modification of operation A to the variable is visible to Operation B. This is not a sequential relationship of code execution time, but a guarantee that the execution results are in order. See the following example to illustrate the current occurrence
?
A,B为共享变量,r1,r2为局部变量初始 A=B=0 Thread1 | Thread21: r2=A | 3: r1=B2: B=2| 4: A=2 |
With the intuitive sense, thread 1 executes r2=a first, then r2=0, then assigns a value b=1, thread 2 executes r1=b, because thread 1 modifies the value of B to 1, so r1=1. However, in the current memory model, it is possible that the end result is r1 = r2 = 2. Why is this, because the compiler or multiprocessor may be disorderly execution of instructions, thread 1 from the code flow above is executed r2 = a, b = 1, but the execution of the processor will be performed at the time of the implementation of R2 = 2, in the execution of a, thread 2 may be executed a = 2, in the execution R1 = B, which may causes r1 = r2 = 2.
Let's take a look at the rules for first-mover relationships.
- 1 in the same thread, in the order in which the code executes (that is, the order of Code semantics), the previous operation occurs before the next operation
- 2 Unlocking a Monitor object precedes the subsequent lock operation on the same monitor object
- 3 Write to the volatile field precedes the read operation of this field later
- 4 The start operation on the thread (the start () method of the calling thread object) precedes any other action on this thread
- All operations in 51 threads call the Join () method on this thread before any other thread
- 6 If the a operation takes precedence over the b,b operation takes precedence over C, then a operation takes precedence over C
Explain the implications of these first-occurrence rules
Rule 1 should be better understood because it is more suitable for people's normal thinking. For example, in the same thread T, the order of the code is as follows:
?
thread 1 shared variable A, B local variable R1, R2 code order 1 : A = 1 2 : R1 = A 3 : B = 2 Code class= "Java value" >4 : r2 = B The result of the implementation is a= 1 ,b= 2 Code class= "Java plain" >,r1= 1 ,r2= 2 |
Because the above is in the same thread, according to rule 1 that is, in code order, A = 1 First occurs R1 =a, then R1 = 1
Look at Rule 2, here's an example of jsr133.
According to Rule 2, because the unlock operation precedes the lock operation, x=1 is visible to thread 2, so r2 = 1
In the analysis below, see this example, because the unlock operation before the lock operation, so the thread x=1 for thread 2 is not necessarily visible (not necessarily the current occurrence), so the value of R2 is not necessarily 1, it is possible that X is assigned to a value of 1 before the state value (assuming that the X initial value is 0, The value of R2 at this point may be 0)
For Rule 3, we can slightly modify the first example we explained
?
a, b is a shared variable, and B is a valotile type of r1,r2 as local variable initial a=b= 0 thread1 | Thread2 1 : r2=a | 3 : r1=b 2 : b= 2 | 4 : a= 2 so r1 = 2 Code class= "Java Plain", R2 may be 0 2 |
Because for variable B of the volatile type, thread 1 updates to B immediately thread 2 is visible, so the value of R1 is determined. Because A is a non-valotile type, the value is indeterminate.
Rule 4,5,6 Here does not explain, know the rules can be.
As can be seen from the above, the rules that occur first have great flexibility, and the compiler can reorder the instructions to meet the needs of processor performance. As soon as the results are reordered, execution results are visible within a single thread (i.e., the same thread satisfies the first occurrence principle 1).
The Java memory model is built on top of the pre-occurring memory model, and on top of that, some enhancements are made. Because the current occurrence is a weakly constrained memory model, when multithreading competes to access shared data, it can lead to unexpected results. Some of the Java memory models are acceptable, and some are not acceptable to the Java memory model. Details are not specified here. This shows only the important points of the new memory model for Java.
The semantics of the final field
In Java, if a class defines a final property, then this property cannot be changed after initialization. It is generally assumed that the final field is unchanged. In the Java memory model, there is a special treatment for final. If a class C defines a non-static final property A, and a non-static final property B, Initializes a A, a, in the constructor of C, if a thread T1 creates an object of Class C, the thread T2 accesses the A and B properties of the Co object at the same time, If T2 obtains a Co object that has already been constructed, then the value of property A can be determined, and the value of property B may not be initialized.
The following code illustrates the situation
?
public class FinalVarClass { public final int a ; public int b = 0; static FinalVarClass co; public FinalVarClass(){ a = 1; b = 1; } //线程1创建FinalVarClass对象 co public static void create(){ if(co == null){ co = new FinalVarClass(); } } //线程2访问co对象的a,b属性 public static void vistor(){ if(co != null){ System.out.println(co.a);//这里返回的一定是1,a一定初始化完成 System.out.println(co.b);//这里返回的可能是0,因为b还未初始化完成 } }} |
Why this happens, possibly because the processor re-sorts the instructions that created the object. Under normal circumstances, the object creation statement CO = new Finalvarclass () is not atomic, in simple terms, can be divided into several steps, 1 allocates memory space 2 creates an empty object 3 Initializes an empty object 4 points the initialized object reference to the Co, because these steps the processor may execute concurrently, For example, 3, 4 concurrent execution, so after the create operation completes, the co does not necessarily initialize immediately, so when the Vistor method, the value of B may not be initialized. However, if it is the final field, you must ensure that initialization is complete before the corresponding return reference.
Volatile semantics
For volatile fields, as already described in the current occurrence rule, the write operation of the volatile variable is preceded by the read operation of the variable. This means that any modification to the volatile variable can be reflected in other threads.
In the Java garbage collection article, the allocation of memory at runtime of the JVM is described. One of the memory areas is the JVM virtual machine stack, and each thread runs with a line stacks,
Line stacks saves the variable value information when the thread runs. When a thread accesses an object, the value of the variable that corresponds to the heap memory is first found by the object's reference, and then the heap memory
The specific value of the variable is load into the thread's local memory, a copy of the variable is created, and then the thread is no longer related to the object in the heap memory variable value, but directly modifies the value of the replica variable.
At some point after the modification (before the thread exits), the value of the thread variable copy is automatically written back to the object in the heap variable. This changes the value of the object in the heap. Here is a picture
Describe this write interaction
Read and load copying variables from main memory to current working memory use and assign execute code, change shared variable value store and write refresh main memory related content with work-based data
Where use and assign can appear multiple times
However, these operations are not atomic, that is, after read load, if the main memory count variable is modified, the value in the thread's working memory will not change because it has been loaded, so the computed result will not be the same as expected
For volatile-modified variables, the JVM virtual machine simply guarantees that the value from main memory load to the working memory of the thread is up-to-date
For example, if thread 1, thread 2 in the read,load operation, found that the value of count in main memory is 5, then the latest value will be loaded
After the thread 1 heap count is modified, it is write to main memory, and the count variable in main memory becomes 6
Thread 2 because the read,load operation has been performed, after the operation, the main memory will also be updated count of the variable value of 6
When two threads are modified with the volatile keyword in a timely manner, there is still a concurrency situation.
Volatile in Java's new memory specification also reinforces the new semantics. In the old memory specification, the order of volatile variables and non-volatile variables can be reordered. As an example,
?
public class VolatileClass { intx = 0; volatile boolean v = false; //线程1write public void writer() { x = 42; v = true; } //线程2 read public void reader() { if (v == true) { System.out.println(x);//结果可能为0,可能为2 } }} |
Thread 1 calls the writer method first, writes to X and V, thread reader judges, and prints X if v=true. In the old memory specification, it is possible to change the order of V and X, which causes the write operation of V to precede the write operation of X, while another thread determines the result of V, because the write operation of v precedes the read operation of V, so if (v==true) returns True, the program executes the print x, At this point x does not necessarily precede the SYSTEM.OUT.PRINTLN directive. So the result may be 0, not necessarily 2.
But the new Java memory model jsr133 This problem, and for volatile semantic variables, automatic lock and unlock operations surround the read and write operations of variable volatile. Then the order of the above statements can be expressed as
?
thread1 thread21 :write x=15:lock(m)2 :lock(m) 6:read v3 :write v=true7:unlock(m)4 :unlock 8 :if(v==true) 9: System.out.print(x) |
Because the unlock operation precedes the lock operation, the x write operation 5 is preceded by the read operation of X 9
These are just a few summary lines in the JSR specification, because the JSR133 specification defines a lot of terminology and many inferences, the above is simply a few of the more important content, the details can refer to the JSR specification public view:http://today.java.net/ Pub/a/today/2004/04/13/jsr133.html
Http://www.cnblogs.com/aigongsi/archive/2012/04/26/2470296.html
Java Memory Model-JSR133 specification introduction (GO)