프로그램을 실행할 때 프로세서와 컴파일러는 성능 향상을 위해 명령을 다시 정렬하는 경우가 많지만 원하는 대로 정렬할 수는 없습니다.
1. 단일 스레드 환경에서는 프로그램 실행 결과를 변경할 수 없습니다.
2. 데이터 종속성이 있는 경우 재정렬이 허용되지 않습니다.
LZ의 이전 내용을 읽어본 경우 블로그를 통해 실제로 이 두 가지 사항은 한 가지에 기인할 수 있다는 것을 알게 될 것입니다. 이는 사전 발생 원칙을 통해 추론할 수 없으며 JMM은 임의의 순서를 허용합니다.
as-if-serial 의미는 최적화를 위해 모든 작업을 재정렬할 수 있지만 재정렬 후에 실행되도록 해야 함을 의미합니다. 결과는 변경할 수 없습니다. 컴파일러, 런타임 및 프로세서는 직렬형 의미 체계를 준수해야 합니다. as-if-serial은 단일 스레드 환경만 보장하며 다중 스레드 환경에서는 유효하지 않습니다.
간단한 예를 사용하여 설명하겠습니다.
int a = 1 ; //A int b = 2 ; //B int c = a + b; //C
A, B, C의 세 가지 작업에는 다음과 같은 관계가 있습니다. A와 B는 데이터 종속성이 없으며 A와 C는 B와 C는 데이터 종속 관계를 가지므로 재정렬 시 A와 B가 임의로 정렬될 수 있지만 반드시 C 앞에 와야 합니다. 실행 순서는 A –> B –> C 또는 B –> A입니다. –> 그러나 실행 순서에 관계없이 최종 결과 C는 항상 3입니다.
as-if-serail 의미론은 단일 스레드 프로그램을 보호하여 재정렬을 전제로 프로그램의 최종 결과가 항상 일관되도록 보장할 수 있습니다.
사실 위 코드의 경우에는 다음과 같은 사전 발생 관계가 있습니다.
A가 B 이전에 발생
B가 C보다 먼저 발생
A가 C보다 먼저 발생
1과 2는 프로그램 순서 규칙이고 3은 이행성입니다. 그러나 재정렬을 통해 B가 A보다 먼저 실행될 수 있다는 뜻이 아닙니까? A가 B보다 먼저 발생하는 이유는 무엇입니까? 여기서 다시 A가 B보다 먼저 발생한다는 것은 A가 반드시 B보다 먼저 실행된다는 것이 아니라 A가 B에게 표시되지만 이 프로그램과 관련하여 A의 실행 결과가 B에게 표시될 필요는 없다는 의미입니다. , 재정렬은 결과에 영향을 미치지 않습니다. 따라서 JMM은 이러한 재정렬을 불법으로 간주하지 않습니다.
우리는 이것을 이해해야 합니다: 프로그램의 실행 결과를 변경하지 않고 프로그램의 운영 효율성을 최대한 향상시킵니다.
여기서 흥미로운 코드를 살펴보겠습니다.
public class RecordExample1 { public static void main(String[] args){ int a = 1; int b = 2; try { a = 3; //A b = 1 / 0; //B } catch (Exception e) { } finally { System.out.println("a = " + a); } } }
재순서 규칙에 따라 작업 A와 작업 B가 재정렬되면 B에서 예외가 발생합니다. (/ 0으로), 명령문 A는 이때 확실히 실행되지 않을 것이므로 a는 여전히 3과 같을까요? 마치 직렬 원리를 따르는 경우 프로그램 결과가 변경됩니다. 실제로 JVM은 직렬 방식의 의미를 보장하기 위해 예외에 대한 특수 처리를 수행합니다. JIT는 재정렬 중에 catch 문에 오류 보상을 삽입합니다. = 3) 이렇게 하면 cathc의 논리가 복잡해지기는 하지만 JIT 최적화 원칙은 catch 블록 논리의 복잡성을 희생하더라도 프로그램의 정상적인 작동에서 논리를 최대한 최적화하는 것입니다.
직렬적 의미론으로 인해 단일 스레드 환경에서는 재정렬이 최종 결과에 영향을 미칠 수 없지만 멀티스레드 환경은 어떻습니까? ?
다음 코드(휘발성의 일반적인 사용법):
public class RecordExample2 { int a = 0; boolean flag = false; /** * A线程执行 */ public void writer(){ a = 1; // 1 flag = true; // 2 } /** * B线程执行 */ public void read(){ if(flag){ // 3 int i = a + a; // 4 } } }
스레드 A는writer()를 실행하고, 스레드 B는 read()를 실행하며, 스레드 B는 실행 중에 a = 1을 읽을 수 있습니까? 정답은 꼭 그런건 아닙니다(참고: X86 CPU는 쓰기-쓰기 재정렬을 지원하지 않습니다. x86에서 작동한다면 당연히 a=1이 될 것입니다. LZ에서는 오랫동안 테스트하지 않았는데 결국 알아냈습니다. 정보 확인 후 ).
작업 1과 작업 2 사이에는 데이터 종속성이 없으므로 재정렬을 수행할 수 있습니다. 작업 3과 작업 4 사이에는 데이터 종속성이 없습니다. 재정렬도 가능하지만 작업 3과 4에는 작업 간의 종속성을 제어합니다. 4. 작업 1과 작업 2의 순서가 변경되는 경우:
이 실행 순서에 따르면 스레드 B는 스레드 A가 설정한 a 값을 확실히 읽을 수 없습니다. 여기 멀티스레딩은 재정렬로 인해 소멸되었습니다.
작업 3과 작업 4의 순서도 변경할 수 있으며 여기서는 설명하지 않습니다. 하지만 둘 사이에는 제어 종속 관계가 존재합니다. 왜냐하면 작업 4는 작업 3이 성립되어야만 실행되기 때문입니다. 코드에 제어 종속성이 존재하면 명령 시퀀스 실행의 병렬성에 영향을 미치므로 컴파일러와 프로세서는 실행 추측을 사용하여 병렬성에 대한 제어 종속성의 영향을 극복합니다. 연산 3과 연산 4를 재정렬하고 연산 4를 먼저 실행하면 계산 결과가 재정렬 버퍼에 임시 저장됩니다. 연산 3이 true이면 계산 결과가
을 통해 변수 i에 기록됩니다. 위 분석에 따르면 재정렬은 단일 스레드 환경의 실행 결과에 영향을 미치지 않지만 다중 스레드의 실행 의미를 파괴합니다.
위 내용은 [Java Concurrency]----Reordering of Java Memory Model 관련 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!