What is JMM?

The JVM is short for Java Memory Model. The Java Memory Model is designed to mask differences in Memory access between hardware and operating systems, so that Java programs can achieve consistent Memory access across platforms.

reorder

In many cases, access to program variables (object instance fields, class static fields, and array elements) may be performed in a different order than the order specified by the program.The compiler can adjust the order of instructions at will in the name of optimization. In some cases, the processor may execute instructions out of order. Data can be moved between registers, processor cache, and main memory in a different order than the program specifies.

Create reorder?

Here is a simple example of an instruction reordering problem after I run it several times. The code is as follows:

/** * when m1 and m2 are reordered, execute x=b, y=a; After executing a=1, b=1, x=0, y=0 * *@author xx
 * @dateThe 2021-08-31 * /
public class JmmTest {

    private int a = 0;
    private int b = 0;
    private int x = 1;
    private int y = 1;

    public void m1(a) {
        a = 1;
        x = b;
    }

    public void m2(a) {
        b = 1;
        y = a;
    }

    public void test(a) throws Exception {
        CyclicBarrier cyc = new CyclicBarrier(2);
        Thread thread1 = new Thread(() -> {
            try {
                cyc.await();
            } catch (Throwable e) {
                e.printStackTrace();
            }
            m1();
        }, "A");

        Thread thread2 = new Thread(() -> {
            try {
                cyc.await();
            } catch (Throwable e) {
                e.printStackTrace();
            }
            m2();
        }, "B");


        thread1.start();
        thread2.start();
        thread1.join();
        thread1.join();

        if (x == 0 && y == 0) {
            System.out.println("Reordered..."); }}public static void main(String[] args) throws Exception {
        for (int i = 0; i < 100000; i++) {
            JmmTest jmmTest = newJmmTest(); jmmTest.test(); }}}// Output the resultReordering... Reordering... Reordering... Reordering...Copy the code

The final keyword

A final modifier before a basic type indicates that the modified variable is a constant and cannot be modified. A field that is both static and final occupies only a portion of storage that cannot be changed.

When final is applied to an object, final holds the application constant. Once a reference is initialized to one object, it cannot be changed to point to another object.

How does the final keyword work?

The value of the final field of an object is set in its constructor. Assuming the object is constructed “correctly,” once the object is constructed, the value assigned to the final field in the constructor will be visible to all other threads without synchronization. In addition, the visible values of any other objects or arrays referenced by these final fields will be at least as up-to-date as the final fields.

What does it mean to construct objects correctly? It simply means that references to the object being constructed are not allowed to “escape” during construction. In other words, do not place a reference to an object you are constructing anywhere that another thread might be able to see it; Don’t assign it to a static field, don’t register it as a listener for any other object, and so on. These tasks should be done after the constructor completes, not in the constructor.

Here’s an example:

class FinalFieldExample {
    final int x;
    int y;
    static FinalFieldExample f;

    public FinalFieldExample(a) {
        x = 3;
        y = 4;
    }

    static void writer(a) {
        f = new FinalFieldExample();
    }

    static void reader(a) {
        if(f ! =null) {
            int i = f.x;
            intj = f.y; }}}Copy the code

The above class is an example of how final fields can be used. The thread executing reader must see the fx value of 3 because it is final. I’m not guaranteed to see the y value of 4, because it’s not final. If the FinalFieldExample constructor looks like this:

public FinalFieldExample(a) { // bad!
  x = 3;
  y = 4;
  // bad construction - allowing this to escape
  global.obj = this;
}
Copy the code

Then the readout of this reference thread from global.obj is not guaranteed to see x =3.

The ability to see the value of a correctly constructed field is great, but if the field itself is a reference, you also want your code to see the latest value of the object (or array) to which it points. This is also guaranteed if your field is the final field. Thus, you can have a final pointer to an array without worrying that other threads will see the correct value for the array reference, but the wrong value for the array contents. Again, “correct” here means “the latest value as of the end of the object constructor,” not “the latest value available.”

Now, having said that, if after one thread has constructed an immutable object (that is, an object that contains only final fields), you want to make sure that it is seen correctly by all other threads, you usually still need to use synchronization. For example, there is no other way to ensure that a reference to an immutable object can be seen by a second thread. The assurance that your program gets from final fields should be fine-tuned with a deep and careful understanding of how concurrency is managed in your code.

The volatile keyword

Volatile fields are special fields used to communicate state between threads. Each read on volatile will see the last write to that volatile by any thread; In fact, programmers specify them as fields because cached or reordered results never allow “stale” values to be seen. Disallow the compiler and runtime from assigning them in registers. They must also ensure that after writing, they are flushed from the cache to main memory so that other threads can see them immediately. Similarly, before volatile fields can be read, the cache must be invalidated so that you see values in main memory rather than those in the local processor cache. There are additional restrictions on reordering volatile variable access.

Under the old memory model, access to volatile variables could not be reordered to each other, but could be reordered using non-volatile variable access. This undermines the usefulness of volatile fields as a means of sending conditional signals from one thread to another.

It is still true that mutable variables cannot be reordered with each other under the new memory model. The difference is that it is no longer so easy to reorder normal field access around them. Writing 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 gain. In fact, because the new memory model imposes stricter restrictions on the reordering of volatile field access and other field access (volatile or not), anything that is visible when thread A writes volatile field F becomes visible when thread B reads f.

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

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

DCL singleton pattern

DCL(double-checked-locking) is used to delay the initialization of singleton objects to reduce the memory overhead during initialization. The following is an example of realizing the singleton pattern with double locking:

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

private static Something instance = null;

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

Copy the code

** it doesn’t look like it, but it’s wrong. ** The most obvious reason is that writes to initializing instance and writes to the instance field can be reordered by the compiler or cache, which would have the effect of returning what appears to be a partial construct. The result is that we read an uninitialized object. There are many other reasons why this is wrong, and why the algorithm for it is wrong.

Many assumed that the use of the volatile keyword would eliminate problems when attempting double-checked locking. In JVMS prior to 1.5, volatile was not guaranteed to work. Under the new memory model, making the instance field volatile “fixes” the double-checked locking problem, because there is a happens-before relationship between the constructor thread initializing Something and returning its value to the thread that reads it.

We recommend using Initialization On Demand Holder, which is thread-safe and easier to understand:

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance(a) {
  return LazySomethingHolder.something;
}
Copy the code

This code is guaranteed to be correct due to the initialization guarantee of static fields; If a field is set in a static initializer, it is guaranteed to be correctly visible to any thread accessing the class.

The resources

  • www.cs.umd.edu/~pugh/java/…
  • Tutorials.jenkov.com/java-concur…
  • www.jianshu.com/p/e6cda45d5…