JMM Cookbook(一)指令重排

+关注继续查看

指令重排

Volatile与监视器

JMM中关于volatile和监视器主要的规则可以被看作一个矩阵。这个矩阵的单元格表示在一些特定的后续关联指令的情况下，指令不能被重排。下面的表格并不是JMM规范包含的，而是一个用来观察JMM模型对编译器和运行系统造成的主要影响的工具。

 能否重排 第二个操作 第一个操作 Normal Load Normal Store Volatile load MonitorEnter Volatile store MonitorExit Normal Load Normal Store No Volatile load MonitorEnter No No No Volatile store MonitorExit No No

• Normal Store指令包括：对非volatile字段的存储，putfield，putstatic和array store；
• Volatile store指令包括：对多线程环境的volatile变量的存储，putfield，putstatic；
• MonitorEnters指令（包括进入同步块synchronized方法）是用于多线程环境的锁对象；
• MonitorExits指令（包括离开同步块synchronized方法）是用于多线程环境的锁对象。

JSR-133规范规定上述关于volatile和监视器的规则仅仅适用于可能会被多线程访问的变量或对象。因此，如果一个编译器可以最终证明（往往是需要很大的努力）一个锁只被单线程访问，那么这个锁就可以被去除。与之类似的，一个volatile变量只被单线程访问也可以被当作是普通的变量。还有进一步更细粒度的分析与优化，例如：那些被证明在一段时间内对多线程不可访问的字段。

Final 字段

1. 如果在构造函数中有一条final字段的store指令，同时这个字段是一个引用，那么它将不能与构造函数外后续可以让持有这个final字段的对象被其他线程访问的指令重排。例如：你不能重排下列语句：
 1 x.finalField = v;
 2 ... ;
 3 sharedRef = x;

这条规则会在下列情况下生效，例如当你内联一个构造函数时，正如“…”的部分表示这个构造函数的逻辑边界那样。你不能把这个构造函数中的对于这个final字段的store指令移动到构造函数外的一条store指令后面，因为这可能会使这个对象对其他线程可见。（正如你将在下面看到的，这样的操作可能还需要声明一个内存屏障）。类似的，你不能把下面的前两条指令与第三条指令进行重排：

 1 x.afield = 1; x.finalField = v; ... ; sharedRef = x;
由于这两条指令是依赖的，编译器将不会对这样的指令进行重排。但是，这条规则会对某些处理器有影响。

—————————————————————————————————————————–

Reorderings

For a compiler writer, the JMM mainly consists of rules disallowing reorderings of certain instructions that access fields (where “fields” include array elements) as well as monitors (locks).

Volatiles and Monitors

The main JMM rules for volatiles and monitors can be viewed as a matrix with cells indicating that you cannot reorder instructions associated with particular sequences of bytecodes. This table is not itself the JMM specification; it is just a useful way of viewing its main consequences for compilers and runtime systems.

 Can Reorder 2nd operation 1st operation Normal Load Normal Store Volatile load MonitorEnter Volatile store MonitorExit Normal Load Normal Store No Volatile load MonitorEnter No No No Volatile store MonitorExit No No

Where:

• Normal Stores are putfield, putstatic, array store of non-volatile fields
• Volatile Loads are getfield, getstatic of volatile fields that are accessible by multiple threads
• Volatile Stores are putfield, putstatic of volatile fields that are accessible by multiple threads
• MonitorEnters (including entry to synchronized methods) are for lock objects accessible by multiple threads.
• MonitorExits (including exit from synchronized methods) are for lock objects accessible by multiple threads.

The cells for Normal Loads are the same as for Normal Stores, those for Volatile Loads are the same as MonitorEnter, and those for Volatile Stores are same as MonitorExit, so they are collapsed together here (but are expanded out as needed in subsequent tables). We consider here only variables that are readable and writable as an atomic unit — that is, no bit fields, unaligned accesses, or accesses larger than word sizes available on a platform.

Any number of other operations might be present between the indicated 1st and 2nd operations in the table. So, for example, the “No” in cell [Normal Store, Volatile Store] says that a non-volatile store cannot be reordered with ANY subsequent volatile store; at least any that can make a difference in multithreaded program semantics.

The JSR-133 specification is worded such that the rules for both volatiles and monitors apply only to those that may be accessed by multiple threads. If a compiler can somehow (usually only with great effort) prove that a lock is only accessible from a single thread, it may be eliminated. Similarly, a volatile field provably accessible from only a single thread acts as a normal field. More fine-grained analyses and optimizations are also possible, for example, those relying on provable inaccessibility from multiple threads only during certain intervals.

Blank cells in the table mean that the reordering is allowed if the accesses aren’t otherwise dependent with respect to basic Java semantics (as specified in theJLS). For example even though the table doesn’t say so, you can’t reorder a load with a subsequent store to the same location. But you can reorder a load and store to two distinct locations, and may wish to do so in the course of various compiler transformations and optimizations. This includes cases that aren’t usually thought of as reorderings; for example reusing a computed value based on a loaded field rather than reloading and recomputing the value acts as a reordering. However, the JMM spec permits transformations that eliminate avoidable dependencies, and in turn allow reorderings.

In all cases, permitted reorderings must maintain minimal Java safety properties even when accesses are incorrectly synchronized by programmers: All observed field values must be either the default zero/null “pre-construction” values, or those written by some thread. This usually entails zeroing all heap memory holding objects before it is used in constructors and never reordering other loads with the zeroing stores. A good way to do this is to zero out reclaimed memory within the garbage collector. See the JSR-133 spec for rules dealing with other corner cases surrounding safety guarantees.

The rules and properties described here are for accesses to Java-level fields. In practice, these will additionally interact with accesses to internal bookkeeping fields and data, for example object headers, GC tables, and dynamically generated code.

Final Fields

Loads and Stores of final fields act as “normal” accesses with respect to locks and volatiles, but impose two additional reordering rules:

1. A store of a final field (inside a constructor) and, if the field is a reference, any store that this final can reference, cannot be reordered with a subsequent store (outside that constructor) of the reference to the object holding that field into a variable accessible to other threads. For example, you cannot reorder
x.finalField = v; ... ; sharedRef = x;
This comes into play for example when inlining constructors, where “...” spans the logical end of the constructor. You cannot move stores of finals within constructors down below a store outside of the constructor that might make the object visible to other threads. (As seen below, this may also require issuing a barrier). Similarly, you cannot reorder either of the first two with the third assignment in:
v.afield = 1; x.finalField = v; ... ; sharedRef = x;
2. The initial load (i.e., the very first encounter by a thread) of a final field cannot be reordered with the initial load of the reference to the object containing the final field. This comes into play in:
x = sharedRef; ... ; i = x.finalField;
A compiler would never reorder these since they are dependent, but there can be consequences of this rule on some processors.

These rules imply that reliable use of final fields by Java programmers requires that the load of a shared reference to an object with a final field itself be synchronized, volatile, or final, or derived from such a load, thus ultimately ordering the initializing stores in constructors with subsequent uses outside constructors.

10077 0
windows server 2008阿里云ECS服务器安全设置

9159 0

13884 0

7496 0

7365 0

4503 0

22400 0
+关注
ali清英

614

0

《2021云上架构与运维峰会演讲合集》

《零基础CSS入门教程》

《零基础HTML入门教程》