>  기사  >  Java  >  Java 명령어 재정렬 문제 해결

Java 명령어 재정렬 문제 해결

黄舟
黄舟원래의
2017-09-28 09:23:472042검색

아래 편집자는 Java 명령 재정렬 문제에 대한 간단한 토론을 제공합니다. 편집자님이 꽤 좋다고 생각하셔서 지금 공유하고 모두에게 참고용으로 드리고자 합니다. 에디터를 따라가서 살펴봅시다

명령어 재정렬은 비교적 복잡하고 다소 믿을 수 없는 문제입니다. 또한 예제로 시작합니다(실제로 재현할 수 있는 예제를 실행하는 것이 좋습니다. 재정렬 가능성은 여전히 ​​있습니다. 꽤 높음), 지각적 이해력이 있어요


/**
 * 一个简单的展示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");
   }
  }
 }
}

예제는 비교적 간단하고, 코멘트가 추가되었으므로 자세히 설명하지는 않겠습니다.

명령 재정렬이란 무엇인가요? 두 가지 수준이 있습니다.

가상 머신 수준에서 CPU 실행 속도보다 훨씬 느린 메모리 작업 속도로 인해 발생하는 CPU 공백의 영향을 최소화하기 위해 가상 머신은 자체 규칙 중 일부를 따릅니다( 이 규칙은 나중에 설명하겠습니다.) 프로그램 작성 순서를 혼란스럽게 합니다. 즉, 나중에 작성한 코드는 시간 순서대로 먼저 실행되고, 먼저 작성된 코드는 나중에 실행됩니다. 이를 통해 CPU를 최대한 활용합니다. 가능한 한 많이. 위의 예를 들어보겠습니다. a=1의 작업이 아니라 a=new byte[1024*1024](1M 공간 할당)`인 경우 이 때 CPU는 매우 느리게 실행됩니다. 아니면 다음 flag=true를 먼저 실행해야 할까요? 물론, flag=true를 먼저 실행하면 CPU를 미리 사용하고 전반적인 효율성을 높일 수 있습니다. 물론 이 전제는 오류가 발생하지 않는다는 것입니다(어떤 오류에 대해서는 나중에 논의하겠습니다). 여기에는 두 가지 상황이 있습니다. 이전 코드가 먼저 실행되기 전에 나중 코드가 실행되기 시작하지만 효율성이 느려지면 나중 코드가 실행을 시작하고 이전 코드가 실행되기 전에 종료됩니다. 누가 먼저 시작하든, 어떤 경우에는 다음 코드가 먼저 끝날 가능성이 있습니다.

하드웨어 수준에서 CPU는 규칙에 따라 수신된 명령 배치를 재정렬합니다. 이는 또한 CPU 속도가 캐시 속도보다 빠르다는 사실을 기반으로 합니다. 하드웨어 처리의 경우 수신된 제한된 범위의 명령 내에서만 재정렬로 처리할 수 있지만 가상 머신은 더 큰 수준과 더 많은 명령 범위 내에서 재정렬할 수 있습니다. 하드웨어 재정렬 메커니즘은 "JVM 동시성에서 CPU 메모리 재정렬"을 참조하세요.

재순서는 이해하기 매우 어렵습니다. 위에서는 이 개념을 더 잘 이해하려면 몇 가지 예와 차트를 구성해야 합니다. 여기서는 "설명 전 발생"과 "Java 메모리 모델에 대한 심층적 이해 (2) - 재정렬"에 대한 보다 자세하고 생생한 두 가지 기사를 소개합니다. 그중에서도 "as-if-serial"을 마스터해야 합니다. 즉, 아무리 순서를 변경해도 단일 스레드 프로그램의 실행 결과는 변경할 수 없습니다. 컴파일러, 런타임 및 프로세서는 모두 "직렬인 것처럼" 의미 체계를 준수해야 합니다. 간단한 예를 들어보겠습니다.


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

두 문장 a=0과 b=1은 프로그램 논리 결과에 영향을 주지 않고 임의로 정렬할 수 있지만, c=a+b 문장은 처음 두 문장 다음에 실행되어야 합니다.

이전 예에서 볼 수 있듯이 멀티 스레드 환경에서 재정렬 가능성은 상당히 높습니다. 휘발성 및 동기화 키워드는 재정렬을 비활성화할 수 있습니다. 일상적인 프로그래밍 작업에서 재정렬의 단점을 느낍니다.

프로그램 순서 규칙: 스레드 내에서는 코드 순서에 따라 앞에 작성된 작업이 뒤에 작성된 작업보다 먼저 발생합니다. 정확히 말하면 코드순서가 아닌 제어흐름순서가 되어야 하는데, 분기나 루프 등의 구조를 고려해야 하기 때문이다.

모니터 잠금 규칙: 동일한 객체 잠금에 대한 후속 잠금 작업 전에 잠금 해제 작업이 발생합니다. 여기서 강조점은 동일한 잠금에 있으며 "나중에"는 다른 스레드에서 발생하는 잠금 작업과 같은 시간적 순서를 나타냅니다.

휘발성 변수 규칙(Volatile Variable Rule): 휘발성 변수의 쓰기 작업은 변수의 후속 읽기 작업 후에 발생합니다. 여기서 "나중에"는 시간 순서도 나타냅니다.

스레드 시작 규칙: 스레드의 독점적인 start() 메서드는 이 스레드의 모든 작업보다 우선합니다.

스레드 종료 규칙: 스레드의 모든 작업은 스레드가 종료될 때 먼저 발생합니다. Thread.join() 메서드의 끝을 통해 스레드를 감지할 수 있으며 Thread.isAlive()의 반환 값이 종료되었습니다. .

스레드 중단 규칙: 스레드 Interrupte() 메서드에 대한 호출은 중단된 스레드의 코드보다 우선하여 인터럽트 이벤트 발생을 감지합니다. Thread.interrupted() 메서드를 사용하여 스레드가 중단되었는지 여부를 감지할 수 있습니다. 중단된.

객체 마무리 원칙(Finalizer 규칙): 객체 초기화(생성자 실행 끝)는 finalize() 메서드 시작 부분에서 먼저 발생합니다.

전환성: 작업 A가 작업 B보다 먼저 발생하고 작업 B가 작업 C보다 먼저 발생하면 작업 A가 작업 C보다 먼저 발생한다고 결론을 내릴 수 있습니다.

이전에 발생하는 순서를 보장하는 것은 위의 규칙입니다. 위의 규칙이 충족되지 않으면 멀티 스레드 환경에서는 실행 순서가 코드 순서와 동일하다는 보장이 없습니다. 이 스레드에서 관찰되면 모든 작업은 순서대로 이루어집니다. 한 스레드에서 다른 스레드를 관찰하면 위 규칙을 충족하지 않는 모든 작업은 순서가 잘못된 것입니다." 따라서 멀티 스레드 프로그램이 순서에 의존하는 경우 코드 작성 시 위의 규칙을 충족하는지 고려해야 합니다. 그렇지 않은 경우 가장 일반적으로 사용되는 메커니즘은 동기화, 잠금 및 휘발성 수정자입니다.

위 내용은 Java 명령어 재정렬 문제 해결의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.