Understanding Java Memory Model

Introduction

In this article I’m going to talk about java memory model(JMM for short). Java has been widely used in many areas and understanding JMM is the precondition of writing correct and efficient concurrent program. There are many articles on the web which talks about what’s JMM and why we need, but none of them explains how to infer the behavior of program using the JMM, and this is the purpose of this article.

Why java needs memory model?

The references at the end of this article give detailed explanation of this question. In short, memory coherence and instruction reordering are the two main reasons. In modern computers, there are multi processors and each has its own cache, so write is not flushed to main memory immediately and not every read will interact with main memory. To make modification of processor A visible to processor B, compilers needs to insert instructions (which are usually called memory barrier) to force the flush of cache data of processor A to main memory and invalidate cache of processor B. Instruction reordering means that the generated code may not execute in the order they appear in program. Java compiler, JIT compiler and CPU all may do this to your program, so it’s usually incorrect to infer the program behavior based on program order. If you want to know more details about memory coherence and instruction reordering, you can find it here. Languages like C++ relies on memory model provided by underlying platforms. In order to preserve the advantage of “writing once, run everywhere”, java needs a memory model defined in language specification so that programmer does not need to care about the platform java program runs on.

What’s java memory model?

JMM is a set rules which helps programmers to infer the correct behavior of a multithread program. For the optimizations metioned above, there may be multi execution traces for a give program on different platforms, but they all follow some rules, which are determined by java memory model.

Happen before order

The following lists the rules introduced by JMM, essentially they set up the happen-before relationship between two actions in a program:

  1. If x and y are actions of the same thread and x comes before y in program order, then x happens before y.
  2. The complete of constructor of an object happens before the start of the finalization of the same object.
  3. An unlock on a monitor happens before every subsequent lock on that monitor.
  4. A write to a volatile field happens before every subsequent read of that field.
  5. A call to start() on a thread happens before any actions in the started thread.
  6. All actions in a thread happens before any other thread successfully returns from a join() on that thread.
  7. The default initialization of any object happens before any other actions (other than default-writes) of a program.
  8. The final action in a thread T1 happens before any action in another thread T2 that detects that T1 has terminated.
  9. If thread T1 interrupts thread T2, the interrupt by T1 happens before any point where any other thread (including T2) determines that T2 has been interrupted (by having an InterruptedException thrown or by invoking Thread.interrupted or Thread.isInterrupted).
  10. If x, y, z are actions and x happens before y and y happen before z, then x happen before z.

What you should notice here is that happen before does not mean that they have to be executed in that. If action y happens before action x but y does not depend on x, it’s legal to execute y before x. For example, rule 7 says that the default initialization of any object happens before any actions of a program, but JVM implementation is free to put off the execution of default initialization to any point before the first read of the object since the start of a program does not have to depend on that. Also, you should remember that if two actions share a happen-before relationship, they don’t have to appear to happen in that order to any code with which they do not share a happen-before relationship. I’ll use two programs to illustrace how to use happen before order to deduce the behavior of a program.

public class Example1 {
    private int x = 0;
    private volatile boolean b = false;

    public void write() {
        x = 4;
        b = true;
    }

    public void read() {
        while (!b);
        //x is guaranteed to be 4 now.
    }
}

The example above is from JSR 133 FAQ. As the comment in read function says, when b is set to true, it’s guaranteed that x is 4. Let’s try to deduce this with happen before order. Here we assume that thread 1 is executing the write function while thread 2 is executing read function. The action x of x = 4 happens before the action y of b = true, and according to rule 4, action y happens before the action z of “reading that b is set to true”, which is action x happens before action y, and action y happens before action z. According to rule 10, we can deduce that action x happens before action z, which implies that if you read the value of x after action z, it’s guaranteed that you get 4. This example is quite easy to understand and let’s a little more complicated one.

public class Example2 {
    private static Example2 singleton = null;

    private int value;
    private Example2() { value = 1; }

    public static Example2 getSingleton() {
        if (singleton == null) {
            singleton = new Example2();
        }

        return singleton;
    }
}

It’s easy to point out one of the errors of this singleton implementation: since the lack of synchronization, two threads may enter the getSingleton method at the same time and more than one object of Example2 may be allocated. However, even if another thread sees that the singleton field is not null, it may get a half-initialized object of Example2. Let’s explain this using happen before order. This statement of singleton = new Example2(); can be decoupled into three actions:

  1. allocate memory space for object
  2. call constructor of Example2
  3. assign the address of object to single

Let’s assume that both thread 1 and thread 2 are in the getSingleton method. For each thread, actions 1, 2, 3 shares happen before order according to rule 1. However, there doesn’t exist any happen before relationship between codes in thread 1 and thread 2, so actions in thread 1 does not have to appear to happen in happen before order for thread 2. This means that, when thread 2 detects that singleton != null, action 3 of thread 1 happened, but action 2 of thread 1 does not have to complete, in which case thread 2 may get a half-initialized object.

final Field Semantics

The Java Language Specification says that “An object is considered 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.”. According to this statement, if the reference of an object is not leaked out (e.g. assign it to static field, add it to listeners, etc) during its construction, it’s guranteed that all references of the object see the same value of final fields. To explain this, let’s check the class of Example2 again. If the value filed is a final field, then the half-initialization problem mentioned above does not exist since all references of an object is guaranteed to see the same values of final fields after the construction completed.

Atomic Operations

The JMM guarantees that all read and write operations to primitive types and references are atomic except non-volatile long and double. For volatile long and double operations, read and write operations are also atomic.

Conclusion

In this article I talked about what’s java memory model and why we need it. Also I explained how to infer program behavior using rules guaranteed by JMM. Other materials on the web about JMM usually teaches you to think in the implementation of JMM, for example writing volatile variables usually lead to a memory barrier. I think this is the wrong way to use java memory model, and programmers should get used to think in rules rather than implementation.

References

  1. Section 17 of Java Language Specification
  2. JSR 133 FAQ
  3. Java Memory Model From a Programmer’s Point-of-View
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值