많은 온라인 자료는 Java 메모리 모델을 설명합니다.이 메모리 모델은 기본 메모리가 있음을 소개하며 각 작업자 스레드에는 고유 한 작업 메모리가 있습니다. 메인 메모리에는 하나의 데이터가 있고 작업 메모리에는 한 조각이 있습니다. 작업 메모리와 메인 메모리 사이에는 다양한 원자 작업이 동기화됩니다.
다음 사진은이 블로그에서 나온 것입니다
그러나 Java 버전의 지속적인 진화로 인해 메모리 모델도 변경되었습니다. 이 기사는 Java 메모리 모델의 일부 기능에 대해서만 설명합니다. 새로운 메모리 모델이든 이전 메모리 모델이든 이러한 기능을 이해 한 후에는 명확 해 보입니다.
1. 원자력
원자력은 작업이 무질서 수준임을 의미합니다. 여러 스레드가 함께 실행 되더라도 작업이 시작되면 다른 스레드에 의해 방해되지 않습니다.
일반적으로 CPU의 지침은 원자 운영이라고 믿지만, 우리가 쓴 코드는 반드시 원자 연산 일 필요는 없습니다.
예를 들어, i ++. 이 작업은 원자 연산이 아니며 기본적으로 3 개의 작업으로 나누어지고, i를 읽고, +1을 수행하고, 값을 i에 할당합니다.
두 개의 스레드가 있다고 가정합니다. 첫 번째 스레드가 i = 1을 읽으면 +1 작업은 아직 수행되지 않았으며 두 번째 스레드로 전환합니다. 현재 두 번째 스레드는 i = 1을 읽습니다. 그런 다음 두 스레드는 후속 +1 작업을 수행 한 다음 값을 다시 할당합니다. 3은 아니지만 2입니다. 분명히 데이터에 불일치가 있습니다.
예를 들어, 32 비트 JVM에서 64 비트 길이의 값을 읽는 것은 원자 작동이 아닙니다. 물론 32 비트 JVM은 32 비트 정수를 원자 작업으로 읽습니다.
2. 주문
동시에 프로그램 실행이 순서대로있을 수 있습니다.
컴퓨터가 코드를 실행하는 경우 프로그램 순서대로 반드시 실행되는 것은 아닙니다.
클래스 orderexample {int a = 0; 부울 플래그 = 거짓; 공개 void Writer () {a = 1; flag = true; } public void reader () {if (flag) {int i = a +1; }}} 예를 들어, 위의 코드에서는 각각 두 개의 스레드가 각각 호출됩니다. 상식에 따르면, 쓰기 스레드는 먼저 a = 1을 실행 한 다음 flag = true를 실행해야합니다. 읽기 스레드가 읽을 때, i = 2;
그러나 a = 1과 flag = true 때문에 논리적 상관 관계는 없습니다. 따라서 실행 순서를 뒤집을 수 있으며 Flag = True를 먼저 실행 한 다음 A = 1을 실행할 수 있습니다. 이 시점에서 flag = true 일 때, 읽기 스레드로 전환하십시오. 현재 A = 1은 아직 실행되지 않았으며 읽기 스레드는 i = 1입니다.
물론 이것은 절대적인 것이 아닙니다. 순서대로없고 일어나지 않을 수 있습니다.
그렇다면 왜 순서가 없습니까? 이것은 CPU 명령으로 시작합니다. Java의 코드가 컴파일 된 후에는 최종적으로 어셈블리 코드로 변환됩니다.
명령 실행은 여러 단계로 나눌 수 있습니다. CPU 명령이 다음 단계로 나뉘어 있다고 가정합니다.
여기에는 두 가지 지침이 있다고 가정하십시오
일반적으로 말하면, 지침이 연속적으로 실행되고 먼저 명령어 1을 실행 한 다음 지침 2를 실행한다고 생각합니다. 각 단계가 1 CPU 시간이 필요하다고 가정하면이 두 지시 사항을 실행하려면 10 개의 CPU 시간이 필요합니다. 이는 너무 비효율적입니다. 실제로 지침은 병렬로 실행됩니다. 물론, 첫 번째 명령이 실행되는 경우, 명령 등록 등이 동시에 점유 될 수 없기 때문에 두 번째 명령어를 수행 할 수 없습니다. 따라서 위 그림과 같이, 두 지침은 비교적 비틀 거리는 방식으로 병렬로 실행됩니다. 명령 1이 ID를 실행하면 명령어 2가 if를 실행합니다. 이러한 방식으로, 6 개의 CPU 기간 만에 두 가지 지침이 실행되었으며, 이는 비교적 효율적이었습니다.
이 아이디어에 따르면 A = B+C 명령이 어떻게 실행되는지 살펴 보겠습니다.
그림에 표시된 것처럼 추가 작업 중에 유휴 (x) 작동이 있습니다. B와 C를 추가하려면, ADD의 X 작업이 그림에서 X 조작을 읽지 않으면 C가 메모리에서 읽지 않았을 때 C가 완료 될 때 메모리에서 읽지 않았습니다. 여기에 질문이 있습니다. WB)가 R2에 WB (Write Back)가 추가 될 수 없기 때문에 r1과 r1의 기술을 사용하지 않기 때문입니다. 하드웨어이므로 ADD가 수행되기 전에 WB가 실행될 때까지 기다릴 필요가 없습니다). 따라서 추가 작업에는 유휴 상태가 있습니다. SW 운영에서는 EX ADD 명령과 동시에 EX 명령을 수행 할 수 없으므로 유휴 시간이 있습니다.
다음으로 약간 더 복잡한 예를 들어 봅시다
a = b+c
d = ef
해당 지침은 다음과 같습니다
그 이유는 위와 유사하므로 여기서는 분석하지 않습니다. 여기에는 X가 많고 사이클이 낭비되고 성능도 영향을받습니다. XS 수를 줄이는 방법이 있습니까?
ADD는 위의 명령에 따라 데이터 의존성을 갖기 때문에 일부 작업을 사용하여 X의 자유 시간을 채우기를 희망하며 데이터 의존성에 의해 생성 된 자유 시간을 채우기 위해 데이터 의존성없이 일부 지침을 사용하기를 희망합니다.
우리는 지침의 순서를 변경했습니다
지침 순서를 변경 한 후 X가 제거됩니다. 전체 실행 기간도 감소했습니다.
명령어 재정렬은 파이프 라인을 더 매끄럽게 할 수 있습니다
물론, 교육 재 배열의 원칙은 직렬 프로그램의 의미를 파괴 할 수 없다는 것입니다. 예를 들어, a = 1, b = a+1, 이러한 지침은 재 배열의 일련 결과가 원래 결과와 다르기 때문에 재 배열되지 않습니다.
명령 재 배열은 컴파일러 또는 CPU를 최적화하는 방법 일 뿐이며이 최적화로 인해이 장의 시작 부분에서 프로그램에 문제가 발생했습니다.
그것을 해결하는 방법? 휘발성 키워드를 사용하면이 후속 시리즈가 소개됩니다.
3. 가시성
가시성은 스레드가 공유 변수의 값을 수정할 때 다른 스레드가 즉시 수정을 알 수 있는지 여부를 나타냅니다.
가시성 문제는 다양한 링크에서 발생할 수 있습니다. 예를 들어, 방금 언급 한 명령어 재정렬은 가시성 문제를 일으킬 것이며, 또한 컴파일러 최적화 또는 특정 하드웨어의 최적화로 가시성 문제가 발생합니다.
예를 들어, 스레드는 공유 값을 메모리로 최적화하고 다른 스레드는 공유 값을 캐시에 최적화합니다. 메모리의 값을 수정할 때 캐시는 수정을 알지 못합니다.
예를 들어, 일부 하드웨어 최적화는 프로그램이 동일한 주소에 여러 번 쓸 때 불필요하다고 생각하고 마지막 쓰기 만 유지하므로 이전에 작성된 데이터가 다른 스레드에서는 보이지 않습니다.
요컨대, 가시성 문제의 대부분은 최적화에서 비롯됩니다.
다음으로 Java Virtual Machine 레벨에서 발생하는 가시성 문제를 살펴 보겠습니다.
문제는 블로그에서 나옵니다
패키지 edu.hushi.jvm; /** * * * @author -10 * */public class VisibilityTest는 스레드 {private boolean stop; public void run () {int i = 0; while (! stop) {i ++; } system.out.println ( "Finish Loop, i =" + i); } public void stopit () {stop = true; } public boolean getStop () {리턴 스톱; } public static void main (string [] args)은 예외 {VisibilityTest v = new VisibilityTest (); v.start (); Thread.sleep (1000); v.stopit (); Thread.sleep (2000); System.out.println ( "마무리 메인"); System.out.println (v.getStop ()); }} 코드는 매우 간단합니다. V 스레드는 메인 스레드가 정지 메소드를 호출 할 때까지 I ++를 while 루프에서 유지하여 v 스레드의 정지 변수 값을 변경하여 루프를 중지합니다.
간단한 코드가 실행될 때 문제가 발생합니다. 이 프로그램은 스레드가 클라이언트 모드에서 자체 증가 작업을 수행하는 것을 막을 수 있지만 서버 모드에서는 먼저 무한 루프가됩니다. (서버 모드에서 더 많은 JVM 최적화)
64 비트 시스템의 대부분은 서버 모드이며 서버 모드에서 실행됩니다.
마무리 메인
진실
이 두 문장 만 인쇄되지만 마감 루프는 인쇄되지 않습니다. 그러나 정지 값이 이미 사실임을 알 수 있습니다.
이 블로그의 저자는 도구를 사용하여 프로그램을 조립 코드로 복원합니다.
어셈블리 코드의 일부만이 여기에 차단되고 빨간색 부분은 루프 부분입니다. 0x0193BF9D만이 정지 검증이지만 빨간색 부분은 정지 값을 사용하지 않으므로 무한 루프가 수행된다는 것을 분명히 알 수 있습니다.
이것이 JVM 최적화의 결과입니다. 그것을 피하는 방법? Directive 재주문과 마찬가지로 휘발성 키워드를 사용하십시오.
휘발성이 추가되면 어셈블리 코드로 복원하면 각 루프가 정지 값을 얻을 수 있습니다.
다음으로 "Java Language Specification"의 몇 가지 예를 살펴 보겠습니다.
위의 그림은 명령 재 분격이 다른 결과로 이어질 것임을 보여줍니다.
위의 그림에서 R5 = R2가 작성된 이유는 R2 = R1.x, R5 = R1.x이며 컴파일 시간에 R5 = R2에 직접 최적화되기 때문입니다. 결국 결과는 다릅니다.
4. 전기
5. 실 안전의 개념
그것은 특정 함수 또는 기능 라이브러리가 다중 스레드 환경에서 호출되면 각 스레드의 로컬 변수를 올바르게 처리하고 프로그램 기능을 올바르게 완료 할 수 있다는 사실을 나타냅니다.
예를 들어, 처음에 언급 된 I ++ 예제입니다
이로 인해 실 불안정으로 이어질 것입니다.
스레드 안전에 대한 자세한 내용은 이전에 작성한이 블로그를 참조하거나 후속 시리즈를 따르십시오. 또한 관련 콘텐츠에 대해서도 이야기 할 것입니다.