JMM

JMM

What is a memory model, anyway?

In multiprocessor systems, processors generally have one or more layers of memory cache, which improves performance both by speeding access to data (because the data is closer to the processor) and reducing traffic on the shared memory bus (because many memory operations can be satisfied by local caches.) Memory caches can improve performance tremendously, but they present a host of new challenges. What, for example, happens when two processors examine the same memory location at the same time? Under what conditions will they see the same value?

在多核心的系统中,为了加快访问数据的数据,会有多级的缓存(寄存器,L1、L2、L3等,现在cpu的速度的提升,多级缓存的作用甚至大于主频的增加!),这就会导致一个问题,多个处理器取读取同一个内存位置的时候要如何处理?在什么条件下需要读到同一个value?

At the processor level, a memory model defines necessary and sufficient conditions for knowing that writes to memory by other processors are visible to the current processor, and writes by the current processor are visible to other processors. Some processors exhibit a strong memory model, where all processors see exactly the same value for any given memory location at all times. Other processors exhibit a weaker memory model, where special instructions, called memory barriers, are required to flush or invalidate the local processor cache in order to see writes made by other processors or make writes by this processor visible to others. These memory barriers are usually performed when lock and unlock actions are taken; they are invisible to programmers in a high level language.

有些处理器有比较强的内存模型,有些比较弱,但提供了一些instructions,最常见的就是内存屏障,通过比如lock和unlock调用,让开发人员灵活控制。

It can sometimes be easier to write programs for strong memory models, because of the reduced need for memory barriers. However, even on some of the strongest memory models, memory barriers are often necessary; quite frequently their placement is counterintuitive. Recent trends in processor design have encouraged weaker memory models, because the relaxations they make for cache consistency allow for greater scalability across multiple processors and larger amounts of memory.

在强内存模型下开发更容易,不用考虑内存一致性问题。使用weaker moemoory model是个趋势,能提高多核心cpu的scalability,并且使用更大的内存。这里有些不懂,能使用更多内存?具体原因是什么,多核心处理器,如果使用强的内存模型,会严重造成大家的阻塞并发能力。

The Java Memory Model describes what behaviors are legal in multithreaded code, and how threads may interact through memory. It describes the relationship between variables in a program and the low-level details of storing and retrieving them to and from memory or registers in a real computer system. It does this in a way that can be implemented correctly using a wide variety of hardware and a wide variety of compiler optimizations.

JMM描述了程序中的变量和底层的对于变量通过寄存器、内存进行存储和读取的细节。最最关键的是,他屏蔽了各种编译器优化和硬件的不同,提供了一个统一的模型

Java includes several language constructs, including volatile, final, and synchronized, which are intended to help the programmer describe a program’s concurrency requirements to the compiler. The Java Memory Model defines the behavior of volatile and synchronized, and, more importantly, ensures that a correctly synchronized Java program runs correctly on all processor architectures.

java提供了volatile, final, and synchronized,并且在JMM中定义了volatile和synchronized的行为。

换句话说,还是为了那句口号,“Write once, run anywhere”,如果没有JMM规范(一般指JSR-133规范中的),那么可能不同cpu体系、甚至不同编译器会有不同的程序行为,不能忍。

What does synchronization do?

同步,都做啥?一般的,最直观的,就是互斥么。

Synchronization has several aspects. The most well-understood is mutual exclusion – only one thread can hold a monitor at once, so synchronizing on a monitor means that once one thread enters a synchronized block protected by a monitor, no other thread can enter a block protected by that monitor until the first thread exits the synchronized block.

本质上锁,不管是redis还是mysql中的,很多都是这样的,对资源的互斥访问。在java中,最常见的synchronized看上去是锁了个代码块,但是其实也是锁的个资源,一个对象的monitor。

But there is more to synchronization than mutual exclusion. Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor. After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.

but,除了互斥,还有另外一层,在同步块内的写操作在release之前,会flush cashe到main memory中,保证写对其他线程是visiable的。在进入一个同步块的时候,会从主存储中拿到最新的writes。除了这monitor,JMM还有什么能保证么?

The new memory model semantics create a partial ordering on memory operations (read field, write field, lock, unlock) and other thread operations (start and join), where some actions are said to happen before other operations. When one action happens before another, the first is guaranteed to be ordered before and visible to the second. The rules of this ordering are as follows:

the first is guaranteed to be ordered before and visible to the second,首先是要保证顺序一致性,然后才是可见性。这里,仅仅是语义上的order!在具体的处理器实现上,并不一定非要按照顺序来处理,知道保证结果与语义一致即可。

  • Each action in a thread happens before every action in that thread that comes later in the program’s order.
  • An unlock on a monitor happens before every subsequent lock on that same monitor.
  • A write to a volatile field happens before every subsequent read of that same 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.

“Each action in a thread happens before every action in that thread that comes later in the program’s order”,这句其实就是最基础的定理,如果action在program’s order上先于另外一个action,那么这个action是happens-before的,这不是像废话么?其实这个action并不是一般的action,而是几个特定的action(读写volatile、线程start、join),write field是write volatile filed,而对于普通field,可能会重排。

对于“An unlock on a monitor happens before every subsequent lock on that same monitor.”的理解:

This means that any memory operations which were visible to a thread before exiting a synchronized block are visible to any thread after it enters a synchronized block protected by the same monitor, since all the memory operations happen before the release, and the release happens before the acquire.

第一层意思,ordered before,当然后续的lock要在un lock之后了,这比较好理解,但是happens-before还有一层,就是visible,unlock之前的所有操作要是visible对于后续的lock操作,这就是一开始balabala一大堆的意思,用一句话概括了而已。

在看“A write to a volatile field happens before every subsequent read of that same volatile.”,在后续的read volatile时候,write的变量,甚至包括write变量之前的一些内存操作,对于read变量时候,都要是visible的。这里,之前的内存操作,是由部分推断在里面,但新的JMM规范就是这样的。

How do final fields work under the new JMM?

The values for an object’s final fields are set in its constructor. Assuming the object is constructed “correctly”, once an object is constructed, the values assigned to the final fields in the constructor will be visible to all other threads without synchronization. In addition, the visible values for any other object or array referenced by those final fields will be at least as up-to-date as the final fields.

只要对象是正确的构造的(“this没有逃逸”),不需要同步就可以保证任意线程都能看到final域在构造函数中被初始化之后的值。如果没有JMM的这个语义,那么有可能在多线程的场景下,构造函数返回后,还没由来的急对final进行赋值操作,线程已经来访问这个final的值了,JMM通过在final赋值与构造函数之间增加一个StoreStore的屏障保证这两个赋值操作不会被重排。

What does volatile do?

Volatile fields are special fields which are used for communicating state between threads.

**communicating state between threads. **,新的模型中不仅仅会对volatile本身的状态能够进行communicating

Under the old memory model, accesses to volatile variables could not be reordered with each other, but they could be reordered with nonvolatile variable accesses. This undermined the usefulness of volatile fields as a means of signaling conditions from one thread to another.

新的JMM能够当作一个signaling condition,能够防止访问nonvolatile variable与自己的重排!

Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

Thus, if the reader sees the value true for v, it is also guaranteed to see the write to 42 that happened before it.

// double-checked-locking - don't do this!

private static Something instance = null;

public Something getInstance() {
  if (instance == null) {
    synchronized (this) {
      if (instance == null)
        instance = new Something();
    }
  }
  return instance;

以上,著名的双检锁失效的问题,解决方式很简单:

Under the new memory model, making the instance field volatile will “fix” the problems with double-checked locking, because then there will be a happens-before relationship between the initialization of the Something by the constructing thread and the return of its value by the thread that reads it.

Instead, use the Initialization On Demand Holder idiom, which is thread-safe and a lot easier to understand: ``` private static class LazySomethingHolder { public static Something something = new Something(); }

public static Something getInstance() { return LazySomethingHolder.something; } ```

This code is guaranteed to be correct because of the initialization guarantees for static fields; if a field is set in a static initializer, it is guaranteed to be made visible, correctly, to any thread that accesses that class.

在静态初始化块内,field是能够保证可见性和正确性的。因为在getInstance会把类LazySomethingHolder初始化,而初始化这个类是需要class对象的初始化锁的,而java语言规范规定,对于每一个类或接口都需要一个唯一的初始化锁与之对对应。

参考

jsr-133 faq Atomic Access happens-before是什么

Table of Contents