Java 内存模型

所需积分/C币:16 2018-07-23 16:45:07 371KB PDF
收藏 收藏

深入理解 java 内存模型是 java 程序员的必修课,看看原汁原味正宗的内存模型吧
1 3 Wait sets and notification 36 13.1 wait 36 13.2 Notification 38 13.3 Interruptions 13.4 Interactions of Waits, Notification and Interruption 14 Sleep and Yield 39 15 Finalization 39 15.1 Implementing Finalization 40 15.2 Interaction with the memory model 42 List of Figures 1 Surprising results caused by statement reordering 6 2 Surprising results caused by forward substitution 7 3 Ordering by a happens-before relationship 9 4 Example illustrating final field semantics 11 5 Without final fields or synchronization, it is possible for this code to print /usr... 11 678 Behavior allowed by happens-before consistency, but not sequential consistency 15 Happens-Before consistency is not sufficient 16 An unacceptable violation of causality 17 9 A standard reordering 10 Table of commit sets for Figure 9 22 11 An unexpected reordering 23 12 Effects of redundant read elimination 23 13 Writes can be performed early 24 11 Compilers can think hard about when actions are guaranteed to occur 21 15 A complicated inference 25 16 Can Threads 1 and 2 see 42. if Thread 4 didnt write 42? 17 Can Threads 1 and 2 see 412. if Thread 4 didnt write to x? 25 18 Example of reordering of final field reads and reflective change .28 19 Final field example where reference to object is read twice 20 Memory chains in an execution of Figure 19 30 21 Transitive guarantees from final ficlds 30 22 Memory chains in an execution of Figure 21 23 Yet another final field example .,,,,31 21 Memory chains in an execution of Figure 23 .,.32 25 Bytes must not be overwritten by writes to adiacent bytes 34 26 Lack of fairness allows thread 1 to never surrender the cpu 35 27 If we observe print message, Thread 1 must see write to v and terminate 35 1 Introduction Java virtual machines support multiple threads of execution. Threads are represented in Java by the Thread class. The only way for a user to create a thread is to create an object of this class; each Java thread is associated with such an object. a thread will start when the start () method is invoked on the corresponding Thread object The behavior of threads, particularly when not correctly synchronized, can be confusing and counterintuitive. This specification describes the semantics of multithreaded Java programs; it includes rules for which values may be seen by a read of shared memory that is updated by multiple threads. As the specification is similar to the memory models for different hardware architectures these semantics are referred to as the Jaia memory model These semantics do not describe how a multithreaded program should be executed. Rather they describe the behaviors that multithreaded programs are allowed to exhibit. Any execution strategy that generates only allowed behaviors is an acceptable execution strategy 1 Locks Java provides multiple mechanisms for communicating between threads. The most basic of these methods is synchronization, which is implemented using monitors. Each object in Java is associated with a monitor, which a thread can lock or unlock. Only one thread at a time may hold a lock on a monitor. Any othor threads attempting to lock that monitor arc blocked until they can obtain a lock on that monitor A thread t may lock a particular monitor multiple times; each unlock reverses the effect of one lock operation. The synchronized statement computes a reference to an object, it then attempts to perform a lock action on that obicct's monitor and docs not proceed further until the lock action has successfully completed. After the lock action has been performed, the body of the synchronized statement is executed. If execution of the body is ever completed, either normally or abruptly, an unlock action is automatically performed on that same monitor A synchronized method automatically performs a lock action when it is invoked; its body is not cxccutcd until thc lock action has successfully complctcd. If thc mcthod is an instance mcthod it locks the monitor associated with the instance for which it was invoked(that is, the object that will be known as this during execution of the body of the method). If the method is static, it locks the monitor associated with the Class object that represents the class in which the method unlock action is automatically pcrformcd on that samc monitor. either normally or abruptly, an is defined. If execution of the method 's body is ever completed The Java programming language neither prevents nor requires detection of deadlock condi- tions. Programs where threads hold(directly or indirectly) locks on multiple objects should use conventional techniques for deadlock avoidance, creating higher-level locking primitives that dont deadlock if necessary Other mechanisms, such as reads and writes of volatile variables and classes provided in the javautil. concurrent package, provide alternative mechanisms for synchronization Original code Valid compiler transformation Initially, A B Initially, A==B== 0 Thread 1 Thread 2 Thread 1 Thread 2 1:r2=A;3:r1=B B=1 r1= B 2:B=1 4:A=2 r2=A;|A=2 May observe r2== 2. r1==1 May observe r2 == 2, r1 ==1 Figure 1: Surprising results caused by statement reordering 1.2 Notation in Examples The Java memory model is not fundamentally based in the object oriented nature of the Java. programming language. For conciseness and simplicity in our cxamples, we oftcn exhibit codc fragments without class or method definitions, or explicit dereferencing. Most examples consist of two or more threads containing statements with access to local variables, shared global variables or instance fields of an object. We typically use variables names such as r1 or r2 to indicate variables local to a method or thread. Such variables are not accessible by other threads 2 Incorrectly Synchronized Programs Exhibit Surprising behav lorS The semantics of the Java programming language allow compilers and microprocessors to per form optimizations that can interact with incorrectly synchronized code in ways that can produce behaviors that seen paradoxical Consider, for example, Figure 1. This program uses local variables r1 and r2 and shared variables a and b. It may appear that the result r2 = 2, r1 ==1 is impossible. Intuitively either instruction 1 or instruction 3 should come first in an execution. If instruction 1 comes first it should not be able to see the write at instruction 4. if instruction 3 comes first. it should not be cble to see the write at instruction 2 If some execution exhibited this beh aNior, t hen we would know that instruction 4 came before instruction 1 which came hefore instruction 2. which came before instruction 3. which came before instruction 4. This is. on the face of it. absurd However, compilers are allowed to reorder the instructions in either thread when this does not alect the execution of that thread in isolation. If instruction 1 is reordered with instruction 2 then it is easy to see how the result r2==2 and r1== 1 might occur To some programmers, this behavior may seem "broken. However, it should be noted that this code is improperly synchronized there is a write ill one thread e a read of the same variable by another thread and the write and read are not ordered by synchronization When this occurs. it is called a data race. When code contains a data race. counterintuitive results are often possible Several mechanisms can produce the reordering in Figure 1. The just-in-time compiler and the processor may rearrange code. In addition, the memory hierarchy of the architecture on which Original code Valid compiler transformation Initially: p==q,px ==0 Initially: p==9,px ==0 Thread 1 Thread 2 Thread 1 Thread r1 r6= p 1=p r6=p; r2=r1.x;r6.x=3 r2=r1.x;r6.x=3 3 r3=q r 4 r3.x 4=r3 r5=r1.x; 5=r2 Figure 2: Surprising results caused by forward substitution,r4==3 May observe r2 == r5 ==0, r4 == 3? May observe r2 = r5 ==0 a virtual machine is run may make it appear as if code is being reordered. For the purposes of simplicity, we shall simply refer to anything that can reorder code as being a compiler. Source code to bytecode transformation can reorder and transform programs, but must do so only in the ways allowed by this specification Another example of surprising results can be seen in Figure 2. This program is also incorrectly synchronized; it accesses shared memory without enforcing any ordering between those accesses One common compiler optimization involves having the value read for r2 reused for r5: they are both reads of r1 x with no intervening write Now consider the case where the assignment to r6. x in Thread 2 happens between the first read of r1 x and the read of r3. x in Thread 1. If the compiler decides to reuse the value of r2 for the r5, then r2 and r5 will have the valuc 0, and r4 will have the valuc 3. From the pcrspcctivc of the programmer, the value stored at p x has changed from 0 to 3 and then changed back Although this behavior is surprising, it is permitted by most JVMs. Ilowever, it was forbidden by the original java memory model in the JLS and JVMS: this was one of the first indications that Che original JMM needed to be replaced 3 Informal semantics A t bi ctly synchronized to avoid the kinds of counterintuitive behaviors that can be observed when code is reordered. The use of correct synchronization does not ensure that the overall behavior of a program is correct. However, its use does allow a programmer to reason bout the possible behaviors of a program in a simple way; the behavior of a correctly synchronized program is much less dependent on possible reorderings. Without correct synchronization, very strange, confusing and counterintuitive behaviors are possible There are two key ideas to understanding whether a program is correctly synchronized Conflicting Accesses Two accesses(reads of or writes to) the same shared field or array element are said to be conflicting if at least one of the accesses is a write Happens-Before Relationship Two actions can be ordered by a happens-before relationship If one action happens-before another, then the first is visible to and ordered before the second. It should be stressed that a happens-before relationship between two actions does not imply that those actions must occur in that order in a Java implementation. The happens-before relation mostl stresses orderings bet ween two actions that conllict with each other, and defines when dala races takc placc. Therc arc a numbcr of ways to induce a happens- bcforc ordering in a Java program includin Each action in a thread happens-before every subsequent action in that thread An unlock on a monitor happens-before every subsequent lock on that monitor a write to a volatile field happens-before every subsequent read of that volatile A call to start( on a thread happens-before any actions in the started thread All actions in a thread happen-before any other thread successfully returns from a join()on that thread If an action a happens-before an action b, and b happens before an action c, then a happens efore c Happens-before is defined more thoroughly in Section 5 When a program contains two conflicting accesses that are not ordered by a happens-before relationship. it is said lo contain a dala Tace. A correctly synchronized progran is one that has no data raccs(Scction 3. 1 contains a subtle but important clarification) A more subtle example of incorrectly synchronized code can be seen in Figure 3, which shows two different executions of the same program, both of which contain conflicting accesses to shared variables X and Y. The two threads in the program lock and unlock a monitor M1. In the execution shown in Figure 3a, there is a happens-before relationship between all pairs of conflicting accesses However, in the exccution shown in Figurc 3b, thcrc is no happens-bcforc ordering bctwcen thc conflicting accesses to x. because of this, the program is not correctly synchronized s if a program is correctly synchronized, then all executions of the program will appear to be sequentially consistent. This is an extremely strong guarantee for programmers. Once programmers are able to determine that their code contains no data races, they do not need to worry that reorderings will affect them 3.1 Sequential Consistency Sequential consistency is a very strong guarantee that is made about visibility and ordering in an execution of a program. Within a sequentially consistent execution, there is a total order over all individual actions(such as reads and writes which is consistent with the order of the program Each individual action is atomic and is immediately visible to every thread. If a program has no data races, then all executions of the program will appear to be sequentially consistent. As noted before, sequential consistency and/or freedom from data races still allows errors arising from groups of operations that need to be perceived atomically and are not If we were to use sequential consistency as our memory model, many of the compiler and processor optimizations that we have discussed would be illegal. For example, in Figure 2, as soon as the write of 3 to p x occurred, subsequent reads of that location would be required to see that value Having discussed sequential consistency, we can use it to provide an important clarification regarding data races and correctly synchronized programs. A data race occurs in an execution of a Thread 1 Thread 2 X=1 Thread 2 Thread 1 Lock M1 Lock M1 r1=Y Lock M1 r1=Y Unlock m Unlock M1 Unlock M1 r2=X Unlock m1 [2=X (a) Thread 1 acquires lock first b) Thread 2 acquires lock first Accesses to X are ordered by happens-before Accesses to X not ordered by happens-before Figure 3: Ordering by a happens-before relationship progran if there are conflicting actions in Chat execution Chat are not ordered by synchronization A program is corrcctly synchronized if and only if all scqucntially consistcnt cxccutions arc frcc of data races. Programmers therefore only need to reason about sequentially consistent executions to determine if their programs are correctly synchronized A more full and formal treatment of memory model issues for normal fields is given in Sections 4 6. 3.2 Final fields Fields declared final are initialized once, but never changed under normal circumstances. The detailed semantics of final fields are somewhat different from those of normal fields. In particular compilers have a great deal of freedom to move reads of final fields across synchronization barriers and calls to arbitrary or unknown methods. Correspondingly, compilers are allowed to keep the value of a final field cached in a register and not reload it from memory in situations where a non-final field would haye to be reloaded Final fields also allow programmers to implement thread-safe immutable objects without syn- chronization. A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads. This can provide safety guarantees against misuse of an immutable class by incorrect or malicious code Final fields must be used correctly to provide a guarantee of immutability. An object is con sidered to be completely initialized when its constructor finishes. a thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields The usage model for final fields is a simple one. Set the final fields for an object in that object's constructor. Do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed. then when the object is seen by another thread, that thread will always see the correctly constructed version of that objects final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are Figure 4 gives an example that demonstrates how final fields compare to normal fields. The class FinalFieldExample has a final int field x and a non-fina.l int field y. One thread might execute the method writer(), and another might execute the method reader(). Because writer writes f after the object's constructor finishes, the reader () will be guaranteed to see the properly initialized value for fx: it will read Che value 3. However, f y is not final; the reader () method is therefore not guaranteed to see the value 4 for it Final fields are designed to allow for necessary security guarantees. Consider the code in figure 5. String objects are intended to be immutable and string operations do not perform synchroniza- tion. While the String implementation does not have any data races, other code could have data races involving the use of Strings, and the memory model makes weak guarantees for programs that have data raccs. In particular, if the ficlds of thc String class were not final, then it would De possible(although unlikely)that Thread 2 could initially see the default value of 0 for the offset. of the string object, allowing it to compare as equal to /tmp". A later operation on the String object might see the correct offset of 4, so that the String object is perceived as being"/usr Many security features of the Java programming language depend upon Strings being perceived truly immutable, even if malicious code is using data races to pass String references between threads

试读 44P Java 内存模型
限时抽奖 低至0.43元/次
身份认证后 购VIP低至7折
关注 私信
Java 内存模型 16积分/C币 立即下载
Java 内存模型第1页
Java 内存模型第2页
Java 内存模型第3页
Java 内存模型第4页
Java 内存模型第5页
Java 内存模型第6页
Java 内存模型第7页
Java 内存模型第8页
Java 内存模型第9页

试读结束, 可继续读5页

16积分/C币 立即下载