Home  >  Article  >  Java  >  Solving the problem of Java instruction reordering

Solving the problem of Java instruction reordering

黄舟
黄舟Original
2017-09-28 09:23:472042browse

The following editor will bring you a brief discussion on the issue of java instruction reordering. The editor thinks it’s pretty good, so I’ll share it with you now and give it as a reference. Let's follow the editor and take a look.

Command reordering is a relatively complicated and somewhat incredible problem. I also start with an example (it is recommended that you run the example, which can actually be reproduced. The probability of reordering is still quite high), I have a perceptual understanding


/**
 * 一个简单的展示Happen-Before的例子.
 * 这里有两个共享变量:a和flag,初始值分别为0和false.在ThreadA中先给  a=1,然后flag=true.
 * 如果按照有序的话,那么在ThreadB中如果if(flag)成功的话,则应该a=1,而a=a*1之后a仍然为1,下方的if(a==0)应该永远不会为
 * 真,永远不会打印.
 * 但实际情况是:在试验100次的情况下会出现0次或几次的打印结果,而试验1000次结果更明显,有十几次打印.
 */
public class SimpleHappenBefore {
 /** 这是一个验证结果的变量 */
 private static int a=0;
 /** 这是一个标志位 */
 private static boolean flag=false;
 public static void main(String[] args) throws InterruptedException {
  //由于多线程情况下未必会试出重排序的结论,所以多试一些次
  for(int i=0;i<1000;i++){
   ThreadA threadA=new ThreadA();
   ThreadB threadB=new ThreadB();
   threadA.start();
   threadB.start();
   //这里等待线程结束后,重置共享变量,以使验证结果的工作变得简单些.
   threadA.join();
   threadB.join();
   a=0;
   flag=false;
  }
 }
 static class ThreadA extends Thread{
  public void run(){
  a=1;
  flag=true;
  }
 }
 static class ThreadB extends Thread{
  public void run(){
   if(flag){
   a=a*1;
   }
   if(a==0){
   System.out.println("ha,a==0");
   }
  }
 }
}

The example is relatively simple, and comments have been added, so I will not describe it in detail.

What is instruction reordering? There are two levels:

At the virtual machine level, in order to minimize the impact of CPU vacancy caused by the memory operation speed being much slower than the CPU running speed, the virtual machine will follow some of its own The rule (this rule will be described later) disrupts the order in which the program is written - that is, the code written later may be executed first in chronological order, while the code written earlier will be executed later - in order to make full use of the CPU as much as possible. Take the above example: If it is not the operation of a=1, but a=new byte[1024*1024] (allocate 1M space)`, then it will run very slowly. At this time, the CPU is waiting for its execution to end. , or should we execute the following flag=true first? Obviously, executing flag=true first can use the CPU in advance and speed up the overall efficiency. Of course, this premise is that no errors will occur (what kind of errors will be discussed later). Although there are two situations here: the later code starts executing before the previous code; the previous code starts executing first, but when the efficiency is slow, the later code starts executing and ends before the previous code execution. No matter who starts first, there is a possibility that the following code will end first in some cases.

At the hardware level, the CPU will reorder the received batch of instructions according to its rules. This is also based on the reason that the CPU speed is faster than the cache speed. The purpose is similar to the previous point, except that for hardware processing, each Times can only be reordered within the limited range of instructions received, while the virtual machine can be reordered at a larger level and within a wider range of instructions. For the hardware reordering mechanism, please refer to "CPU Memory Reordering from JVM Concurrency"

Reordering is difficult to understand. The above just briefly mentions its scenarios. If you want to have a better understanding This concept requires the construction of some examples and charts. Here are two more detailed and vivid articles "happens-before explanation" and "In-depth understanding of Java memory model (2) - Reordering". Among them, "as-if-serial" should be mastered, that is: no matter how reordered, the execution result of the single-threaded program cannot be changed. The compiler, runtime, and processor all must adhere to "as-if-serial" semantics. Take a simple example,


public void execute(){
 int a=0;
 int b=1;
 int c=a+b;
}

The sentences a=0 and b=1 here can be sorted arbitrarily without affecting the logical result of the program, but c=a+b sentence must be executed after the first two sentences.

As you can see from the previous example, the probability of reordering in a multi-threaded environment is quite high. The keywords volatile and synchronized can disable reordering. In addition, there are some rules. , and it is these rules that prevent us from feeling the disadvantages of reordering in our daily programming work.

Program Order Rule: Within a thread, according to the code order, operations written in the front occur before operations written in the back. To be precise, it should be the control flow sequence rather than the code sequence, because structures such as branches and loops must be considered.

Monitor Lock Rule: An unlock operation occurs before a subsequent lock operation on the same object lock. The emphasis here is on the same lock, and "later" refers to the temporal sequence, such as lock operations that occur in other threads.

Volatile Variable Rule (Volatile Variable Rule): The writing operation of a volatile variable occurs after the reading operation of the variable. The "back" here also refers to the sequence in time.

Thread Start Rule: Thread’s exclusive start() method precedes every action of this thread.

Thread Termination Rule: Every operation in a thread occurs first in the termination detection of this thread. We can end it through the Thread.join() method and the return of Thread.isAlive() The value detects that the thread has terminated execution.

Thread Interruption Rule: The call to the thread interrupte() method takes precedence over the code of the interrupted thread to detect the occurrence of the interrupt event. You can use the Thread.interrupted() method to detect whether the thread has been interrupted. .

Object finalization principle (Finalizer Rule): The initialization of an object (the end of constructor execution) occurs first at the beginning of its finalize() method.

Transitivity: If operation A occurs before operation B, and operation B occurs before operation C, then we can conclude that operation A occurs before operation C.

It is the above rules that guarantee the order of happen-before. If the above rules are not met, then in a multi-threaded environment, there is no guarantee that the execution order is equal to the code order, that is, "if observed in this thread, all Operations are all in order; if you observe another thread in one thread, anything that does not meet the above rules is out of order." Therefore, if our multi-threaded program relies on the order of code writing, then we must consider whether it conforms to If the above rules are not met, some mechanisms must be used to make them comply. The most commonly used ones are synchronized, Lock and volatile modifiers.

The above is the detailed content of Solving the problem of Java instruction reordering. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn