Memory Consistency - happens-before relationship i

2019-01-10 06:46发布

问题:

This question already has an answer here:

  • How to understand happens-before consistent 4 answers

While reading Java docs on Memory Consistency errors. I find points related to two actions that creates happen - before relationship:

  • When a statement invokes Thread.start(), every statement that has a happens-before relationship with that statement also has a happens-before relationship with every statement executed by the new thread. The effects of the code that led up to the creation of the new thread are visible to the new thread.

  • When a thread terminates and causes a Thread.join() in another thread to return, then all the statements executed by the terminated
    thread have a happens-before relationship with all the statements
    following the successful join. The effects of the code in the thread are now visible to the thread that performed the join.

I am not able to understand their meaning. It would be great if someone explain it with a simple example.

回答1:

Modern CPUs don't always write data to memory in the order it was updated, for example if you run the pseudo code (assuming variables are always stored to memory here for simplicity);

a = 1
b = a + 1

...the CPU may very well write b to memory before it writes a to memory. This isn't really a problem as long as you run things in a single thread, since the thread running the code above will never see the old value of either variable once the assignments have been made.

Multi threading is another matter, you'd think the following code would let another thread pick up the value of your heavy computation;

a = heavy_computation()
b = DONE

...the other thread doing...

repeat while b != DONE
    nothing

result = a

The problem though is that the done flag may be set in memory before the result is stored to memory, so the other thread may pick up the value of memory address a before the computation result is written to memory.

The same problem would - if Thread.start and Thread.join didn't have a "happens before" guarantee - give you problems with code like;

a = 1
Thread.start newthread
...

newthread:
    do_computation(a)

...since a may not have a value stored to memory when the thread starts.

Since you almost always want the new thread to be able to use data you initialized before starting it, Thread.start has a "happens before" guarantee, that is, data that has been updated before calling Thread.start is guaranteed to be available to the new thread. The same thing goes for Thread.join where data written by the new thread is guaranteed to be visible to the thread that joins it after termination.

It just makes threading much easier.



回答2:

Consider this:

static int x = 0;

public static void main(String[] args) {
    x = 1;
    Thread t = new Thread() {
        public void run() {
            int y = x;
        };
    };
    t.start();
}

The main thread has changed field x. Java memory model does not guarantee that this change will be visible to other threads if they are not synchronized with the main thread. But thread t will see this change because the main thread called t.start() and JLS guarantees that calling t.start() makes the change to x visible in t.run() so y is guaranteed to be assigned 1.

The same concerns Thread.join();



回答3:

Thread visibility problems may occur in a code that isn't properly synchronized according to the java memory model. Due to compiler & hardware optimizations, writes by one thread aren't always visible by reads of another thread. The Java Memory Model is a formal model that makes the rules of "properly synchronized" clear, so that programmers can avoid thread visibility problems.

Happens-before is a relation defined in that model, and it refers to specific executions. A write W that is proven to be happens-before a read R is guaranteed to be visible by that read, assuming that there's no other interfering write (i.e. one with no happens-before relation with the read, or one happening between them according to that relation).

The simplest kind of happens-before relation happens between actions in the same thread. A write W to V in thread P happens-before a read R of V in the same thread, assuming that W comes before R according to the program order.

The text you are referring to states that thread.start() and thread.join() also guarantee happens-before relationship. Any action that happens-before thread.start() also happens before any action within that thread. Similarly, actions within the thread happen before any actions that appear after thread.join().

What's the practical meaning of that? If for example you start a thread and wait for it to terminate in a non-safe manner (e.g. a sleep for a long time, or testing some non-synchronized flag), then when you'll try reading the data modifications done by the thread, you may see them partially, thus having the risk of data inconsistencies. The join() method acts as a barrier that guarantees that any piece of data published by the thread is visible completely and consistently by the other thread.



回答4:

According to oracle document, they define that The happens-before relationship is simply a guarantee that memory writes by one specific statement are visible to another specific statement.

package happen.before;

public class HappenBeforeRelationship {


    private static int counter = 0;

    private static void threadPrintMessage(String msg){
        System.out.printf("[Thread %s] %s\n", Thread.currentThread().getName(), msg);
    }

    public static void main(String[] args) {

        threadPrintMessage("Increase counter: " + ++counter);
        Thread t = new Thread(new CounterRunnable());
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            threadPrintMessage("Counter is interrupted");
        }
        threadPrintMessage("Finish count: " + counter);
    }

    private static class CounterRunnable implements Runnable {

        @Override
        public void run() {
            threadPrintMessage("start count: " + counter);
            counter++;
            threadPrintMessage("stop count: " + counter);
        }

    }
}

Output will be:

[Thread main] Increase counter: 1
[Thread Thread-0] start count: 1
[Thread Thread-0] stop count: 2
[Thread main] Finish count: 2

Have a look output, line [Thread Thread-0] start count: 1 shows that all counter changes before invocation of Thread.start() are visible in Thread's body.

And line [Thread main] Finish count: 2 indicates that all changes in Thread's body are visible to main thread that calls Thread.join().

Hope it can help you clearly.