Happens-Before ensure visible threads

Foreword

Familiar with concurrent programming in Java knows, JMM happen-before rule (Java memory model), which defines the rules of order and visibility of Java multi-threaded operation, to prevent the impact of high regard sort compiled results of the proceedings.

According to the official statement:

When a variable is read a plurality of threads and at least one thread writes, if the read operation and write operation happens-before relation is not, data will be generated competition.

To guarantee 操作 Bthread to see the 操作 Aresults (whether Aand Bif one thread), then Aand Bmust satisfy the principle of HB between, if not, will likely lead to reorder.

In the absence HB relations can occur reordering problems.

What happens-before rule?

It happens-before relationship between the time of occurrence of two major emphasis on conflict between the order of the action, and the definition of data contention.

  • Program sequence rules : a thread in every movement of the thread behind the actions of the action happens-before.
  • Monitor lock Rule : unlock happens-before operation on a tube side (object lock) on the same tube pass subsequent lock operation.
  • volatile variable rules : read write operations on a volatile happens-before every subsequent field the volatile field.
  • The Join () rule : If the thread A executes operations ThreadB.join () returns successfully, then any of the following happens-before thread B to the thread A in the () operation is successful return from ThreadB.join.
  • Transitivity : If an operation of a happens-before operation to b, and b happens-before operation c, there is a happens-before c.
  • start () rule : If the thread A executes operations ThreadB.start () (start thread B), then A thread ThreadB.start () operation happens-before any action in the thread B.
  • Interrupt rules : calling thread interrupted () method precedes the interrupted thread code detection time of the interruption.
  • Object finalize rules : initialization of an object is completed (constructor executes end) precedes its occurrence finalize () method to start.

Then, another angle construed happens-before: When an operator B A happens-before operation, then the operation of the operation result A shared variable B are visible to the operator. Meanwhile, if the operation B happens-before operation C, the operation result of the operation A shared variable B are visible to the operator.

The principle is to achieve visibility cache protocol and memory barrier. Achieve visibility through cache coherency protocol and memory barrier.

How to synchronize?

Doug Lea in the book "Java Concurrency in Practice", there is the following description:

Because happens-before ordering function is very powerful, so sometimes you can "With (Piggyback)" Visible property of the existing synchronization mechanisms. This requires the program sequence rules happens-before combination with other rules in a certain order (usually a monitor rules or volatile variables), the operation to access the lock variable to be protected is not to be sorted. Because this technology is very sensitive to the order of the statement, so it is prone to error. It is an advanced technology, and only when it is necessary to maximize the performance of certain classes (eg ReetrantLock) when only the technology should be used.

Mentioned in the book: by combining a number of rules happens-before, you can achieve visibility is not a lock to protect variables. But because the technology is sensitive to the order of the statements, and therefore prone to error .

The following will demonstrate how to achieve a variable synchronization rules and procedures by volatile order rules.

/**
* 两个线程间隔打印出 0 – 100 的数字
**/
class ThreadPrintDemo {

  static int num = 0;
  static volatile boolean flag = false;

  public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
      for (; 100 > num; ) {
        if (!flag && (num == 0 || ++num % 2 == 0)) {
          System.out.println(num);
          flag = true;
        }
      }
    }
    );

    Thread t2 = new Thread(() -> {
      for (; 100 > num; ) {
        if (flag && (++num % 2 != 0)) {
          System.out.println(num);
          flag = false;
        }
      }
    }
    );

    t1.start();
    t2.start();
  }
}

The num variable without the use of volatile, there will be visibility problems, namely: t1 thread updated num, t2 thread can not perceive.

In fact, I am beginning to think so, but recent research by HB rule, found volatile modified to remove the num also possible.

We analyze, as shown below:

Here Insert Picture Description

We analyze this chart:

  1. First, red and yellow represent different thread operations.
  2. Red thread to make variable num ++, and then modify the volatile variables, this program is in line with the rules of the order. That is 1 happens-before 2.
  3. Red thread happens-before writing yellow thread reading of the volatile volatile, which is 2 happens-before 3.
  4. Yellow thread reads a volatile variable, and then to do the num ++ variable, in line with the rules of the program order, that is, 3 happens-before 4.
  5. The transfer rule, 1 certainly happens-before 4. Therefore, modifications 1 to 4 is visible.

Note: The results of the rules to ensure a happens-before the next operation of the operation are visible. Therefore, the above applet, thread A modification of num, the thread B is fully aware - even without the use of volatile num modification.

In this way, we help to achieve the principle happens-before synchronous operations on one variable, that is, in a multithreaded environment, to ensure the safety of concurrent modification of shared variables. And does not use Java for this variable primitives: volatile and synchronized and CAS (assuming that count the words).

This may seem unsafe (actually safe), it does not seem easy to understand. Because all this happens-before the bottom of the cache protocol and memory barrier to achieve.

Other rules to achieve synchronization

1. Using the thread termination rules to achieve:

  static int a = 1;

  public static void main(String[] args) {
    Thread tb = new Thread(() -> {
      a = 2;
    });
    Thread ta = new Thread(() -> {
      try {
        tb.join();
      } catch (InterruptedException e) {
        //NO
      }
      System.out.println(a);
    });

    ta.start();
    tb.start();
  }

2. Thread start using the rules to achieve:

 static int a = 1;

  public static void main(String[] args) {
    Thread tb = new Thread(() -> {
      System.out.println(a);
    });
    Thread ta = new Thread(() -> {
      tb.start();
      a = 2;
    });

    ta.start();
  }

These two operations, the visibility can be secured in a variable.

Indeed a subversive idea before. Before the concept, if a variable is not modified volatile or final modification, he read in a multithreaded certainly is not safe (because there will cache), leading to read not up to date.

However, by means of happens-before, we can achieve.

to sum up

Although the title of this article is to happen-before to synchronize operations on shared variables through, but the main purpose is a deeper understanding happen-before, understand his happen-before concept is actually to ensure that multi-threaded environment, the operation for the next operation the result of the operation of the order and visibility.

At the same time, through the use of flexible transfer rules , then the rules are combined, you can synchronize the two threads - to achieve the specified shared variable without using the primitives can also guarantee visibility . While this does not seem very easy to read, but it is also an attempt.

Rules on how to combine the use of synchronization, Doug Lea gives JUC in practice.

Such as older versions of FutureTask inner class Sync (disappeared), modify the volatile variables tryReleaseShared method, tryAcquireShared read volatile variable, which is the use of volatile rules;

By providing the non-volatile result variable before tryReleaseShared, then reads the result variable after tryAcquireShared, which is the use of the program sequence rules.

Thus ensuring the visibility of the result variable. And similar to our first example: the use of the program sequence rules and rules to achieve common volatile variable visibility.

And Doug Lea himself said, this "aid" technology is very easy to make mistakes, to be used with caution. However, in some cases, this "aid" is very reasonable.

In fact, BlockingQueue also "help" a happen-before rules. Remember unlock rules? When the unlock occurs, the internal elements must be visible.

And there are other library operations are "by" the principle happen-before: concurrent containers, CountDownLatch, Semaphore, Future, Executor, CyclicBarrier, Exchanger like.

All in all, words and short:

happen-before 原则是JMM的核心所在,只有满足了happens-before原则才能保证有序性和可见性,否则编译器将会对代码重排序。happen-before甚至将lock和volatile也定义了规则。

By appropriate combination of rules happen-before, you can achieve the correct use of the common shared variables.

Reference link: https: //www.01hai.com/note/av144549

Published 86 original articles · won praise 69 · views 130 000 +

Guess you like

Origin blog.csdn.net/lp284558195/article/details/105231977