Java Concurrent 프로그래밍 시리즈 [미완성] :
• Java 동시성 프로그래밍 : 핵심 이론
• Java 동시 프로그래밍 : 동기화 및 구현 원리
• Java 동시 프로그래밍 : 동기화 된 기본 최적화 (경량 잠금, 바이어스 잠금)
• Java 동시 프로그래밍 : 스레드 간의 공동 작업 (대기/알림/수면/수율/조인)
• Java 동시 프로그래밍 : 휘발성 및 원리 사용
1. 휘발성의 역할
"Java Concurrency Programming : Core Theory"기사에서, 우리는 가시성, 질서 및 원자력의 문제를 언급했습니다. 일반적으로 동기화 된 키워드를 통해 이러한 문제를 해결할 수 있습니다. 그러나 동기화의 원리를 이해하면 동기화 된 것이 비교적 헤비급 작동이며 시스템 성능에 상대적으로 큰 영향을 미친다는 것을 알아야합니다. 따라서 다른 솔루션이있는 경우 일반적으로 동기화 된 사용을 사용하여 문제를 해결하지 않습니다. 휘발성 키워드는 가시성과 질서의 문제를 해결하기 위해 Java에서 제공되는 또 다른 솔루션입니다. 원자력과 관련하여 모든 사람이 오해가 발생하기 쉽다는 점입니다. 휘발성 변수의 단일 읽기/쓰기 작업은 길고 이중 유형 변수와 같은 원자력을 보장 할 수 있지만 본질적으로 I ++는 작업을 두 번 읽고 쓰기 때문에 I ++ 작업의 원자력을 보장 할 수는 없습니다.
2. 휘발성 사용
휘발성 사용과 관련하여 몇 가지 예제를 사용하여 사용 및 시나리오를 설명 할 수 있습니다.
1. 재정렬 방지
가장 고전적인 예 중 하나에서 재정렬 문제를 분석해 봅시다. 모든 사람은 싱글 톤 모델의 구현에 익숙해야하며 동시 환경에서는 일반적으로 DCL (Double Check Locking) 방법을 사용하여 구현할 수 있습니다. 소스 코드는 다음과 같습니다.
package com.paddx.test.concurrent; Public Class Singleton {공개 정적 휘발성 싱글 톤 싱글 톤; / *** 생성자는 비공개이며 외부 인스턴스화 금지*/ private singleton () {}; public static singleton getInstance () {if (Singleton == null) {synchronized (Singleton) {if (singleton == null) {Singleton = new Singleton (); }} 반환 싱글 톤; }}이제 변수 싱글 톤 사이에 휘발성 키워드를 추가 해야하는 이유를 분석해 봅시다. 이 문제를 이해하려면 먼저 객체 구성 과정을 이해해야합니다. 객체를 인스턴스화하는 것은 실제로 세 단계로 나눌 수 있습니다.
(1) 메모리 공간을 할당합니다.
(2) 객체를 초기화하십시오.
(3) 메모리 공간의 주소를 해당 참조에 할당합니다.
그러나 운영 체제는 지침을 재정렬 할 수 있으므로 위의 프로세스도 다음과 같은 프로세스가 될 수 있습니다.
(1) 메모리 공간을 할당합니다.
(2) 메모리 공간의 주소를 해당 참조에 할당합니다.
(3) 객체를 초기화하십시오
이 프로세스가 프로세스 인 경우, 비 초기의 객체 참조가 다중 스레드 환경에 노출되어 예측할 수없는 결과를 초래할 수 있습니다. 따라서이 프로세스의 재정렬을 방지하려면 변수를 유형 휘발성 변수로 설정해야합니다.
2. 가시성을 달성하십시오
가시성 문제는 주로 공유 변수 값을 수정하는 하나의 스레드를 지칭하는 반면 다른 스레드는 볼 수 없습니다. 가시성 문제의 주된 이유는 각 스레드에 자체 캐시 영역 인 스레드 작업 메모리가 있기 때문입니다. 휘발성 키워드는이 문제를 효과적으로 해결할 수 있습니다. 다음 예제를 살펴 보겠습니다. 기능을 알 수 있습니다.
package com.paddx.test.concurrent; public class volatiletest {int a = 1; int b = 2; 공개 void change () {a = 3; b = a; } public void print () {system.out.println ( "b ="+b+"; a ="+a); } public static void main (string [] args) {while (true) {최종 휘발 테스트 = new volatileTest (); 새 스레드 (new Runnable () {@override public void run () {try {strule.sleep (10);} catch (InterpruptedException e) {e.printstacktrace ();} test.change ();}}). start (); 새 스레드 (new Runnable () {@override public void run () {try {thread.sleep (10);} catch (InterpruptedException e) {e.printstacktrace ();} test.print ();}). start (); }}}직관적으로 말하면,이 코드에는 두 가지 가능한 결과가 있습니다 : b = 3; a = 3 또는 b = 2; a = 1. 그러나 위의 코드를 실행하면 (조금 더 오래 걸릴 수도 있음) 이전 두 결과 외에도 세 번째 결과도 있습니다.
...... b = 2; a = 1b = 2; a = 1b = 3; a = 3b = 3; a = 3b = 3; a = 1b = 3; a = 3b = 2; a = 1b = 3; a = 3b = 3; a = 3b = 3; a = 3 ...
왜 b = 3; a = 1과 같은 결과가 나타나는가? 정상적인 상황에서는 먼저 변경 메소드를 실행 한 다음 인쇄 방법을 실행하면 출력 결과는 B = 3이어야합니다. A = 3입니다. 반대로, 인쇄 메소드를 먼저 실행 한 다음 변경 메소드를 실행하면 결과는 b = 2; a = 1이어야합니다. 그렇다면 b = 3; a = 1의 결과는 어떻게 나옵니까? 그 이유는 첫 번째 스레드가 값 a = 3을 수정하지만 두 번째 스레드에는 보이지 않으므로이 결과가 발생하기 때문입니다. A와 B가 모두 휘발성 유형의 변수로 변경되고 실행되면 B = 3; A = 1의 결과는 다시는 나타나지 않습니다.
3. 원자력을 보장하십시오
원자력 문제는 위에서 설명되어 있습니다. 휘발성은 단일 읽기/쓰기의 원자력 만 보장 할 수 있습니다. 이 문제는 JLS에 설명 될 수 있습니다.
17.7 Java 프로그래밍 언어 메모리 모델의 목적을 위해 이중 및 긴 비 원자 처리, 비 휘발성 길이 또는 이중 값에 대한 단일 쓰기는 두 개의 별도의 글로 취급됩니다. 이로 인해 스레드가 하나의 쓰기에서 64 비트 값의 처음 32 비트를 볼 수 있고 다른 쓰기에서 두 번째 32 비트가 표시되는 상황이 발생할 수 있습니다. 참고 문헌에 대한 쓰기 및 읽기는 32 비트 또는 64 비트 값으로 구현되는지 여부에 관계없이 항상 원자입니다. 일부 구현은 64 비트 길이 또는 이중 값으로 단일 쓰기 조치를 인접한 32 비트 값의 두 가지 쓰기 조치로 나누는 것이 편리하다는 것을 알 수 있습니다. 효율성을 위해이 동작은 구현 별입니다. Java Virtual Machine의 구현은 원자 적으로 또는 두 부분으로 길고 이중 값으로 쓰기를 자유롭게 수행 할 수 있습니다. Java Virtual Machine의 구현은 가능한 경우 64 비트 값을 분할하지 않도록 권장됩니다. 프로그래머는 가능한 64 비트 값을 휘발성으로 선언하거나 가능한 복합을 피하기 위해 프로그램을 올바르게 동기화하도록 권장됩니다.
이 구절의 내용은 앞에서 설명한 것과 거의 비슷합니다. 길고 이중의 두 가지 데이터 유형의 작동은 높은 32 비트와 낮은 32 비트의 두 부분으로 나눌 수 있기 때문에 일반 길이 또는 이중 유형은 원자가 아닐 수 있습니다. 따라서 모든 사람은 공유 길이 및 이중 변수를 휘발성 유형으로 설정하는 것이 좋습니다.이 유형의 단일 읽기/쓰기 작업이 어쨌든 원자가 될 수 있습니다.
휘발성 변수가 원자력을 보장하는 문제가 있으며, 이는 쉽게 오해 할 수 있습니다. 이제 우리는 다음 프로그램을 통해이 문제를 보여줄 것입니다.
package com.paddx.test.concurrent; public class volatiletest01 {volatile int i; public void addi () {i ++; } public static void main (String [] args)은 InterruptedException {Final volatileTest01 = new volatileTest01 (); for (int n = 0; n <1000; n ++) {new Thread (new Runnable () {@override public void run () {try {thread.sleep (10);} catch (InterpruptedException e) {e.printstacktrace ();} test01.addi ();}}; } stride.sleep (10000); // 위의 프로그램 실행이 시스템을 완료했는지 확인하기 위해 10 초 동안 기다립니다. out.println (test01.i); }}변수 i에 키워드 휘발성을 추가 한 후이 프로그램은 스레드 안전이라고 잘못 생각할 수 있습니다. 위의 프로그램을 실행해보십시오. 내 지역 실행 결과는 다음과 같습니다.
어쩌면 모두가 결과를 다르게 실행할 수 있습니다. 그러나 휘발성은 원자력을 보장 할 수 없다는 것을 알 수 있어야합니다 (그렇지 않으면 결과는 1000이어야 함). 그 이유는 또한 매우 간단합니다. I ++는 실제로 세 단계를 포함한 복합 작업입니다.
(1) i의 값을 읽으십시오.
(2) 1에 1을 추가하십시오.
(3) i의 값을 메모리로 되돌립니다.
이 세 가지 작업이 원자임을 보장 할 수는 없습니다. Atomicinteger 또는 동기화를 통해 +1 작업의 원자력을 보장 할 수 있습니다.
참고 : Resure.sleep () 메소드는 위의 코드 섹션의 여러 곳에서 실행되었으며 동시성 문제의 가능성을 높이고 다른 효과가 없습니다.
3. 휘발성의 원리
위의 예를 통해 기본적으로 휘발성이 무엇인지, 사용 방법을 알아야합니다. 이제 휘발성 층의 기본 계층이 어떻게 구현되는지 살펴 보겠습니다.
1. 가시성 구현 :
이전 기사에서 언급했듯이 스레드 자체는 기본 메모리 데이터와 직접 상호 작용하지 않지만 스레드의 작업 메모리를 통해 해당 작업을 완료합니다. 이것은 또한 스레드 간의 데이터가 보이지 않는 필수 이유입니다. 따라서 휘발성 변수의 가시성을 달성하기 위해이 측면에서 직접 시작할 수 있습니다. 휘발성 변수와 일반 변수에 대한 작문 작업에는 두 가지 주요 차이점이 있습니다.
(1) 휘발 변수를 수정할 때 수정 된 값은 기본 메모리를 새로 고치게합니다.
(2) 휘발 변수를 수정하면 다른 스레드의 작업 메모리에서 해당 변수 값이 실패하게됩니다. 따라서이 변수의 값을 다시 읽을 때 메인 메모리의 값을 다시 읽어야합니다.
이 두 작업을 통해 휘발성 변수의 가시성 문제를 해결할 수 있습니다.
2. 순서대로 구현 :
이 문제를 설명하기 전에 먼저 Java에서 발생하는 규칙을 이해해 봅시다. JSR 133에서 발생하기 전의 정의는 다음과 같습니다.
한 가지 행동이 다른 행동을하기 전에 두 가지 조치를 주문할 수 있습니다. 한 조치가 다른 행동보다 먼저 발생하면 첫 번째 조치는 두 번째로 보이고 주문됩니다.
평신도의 용어로, A가 발생하는 경우 B가 발생하면 A는 A가 B로 표시됩니다. (이 단어는 전과 후에 쉽게 오해되기 때문에 모든 사람은 이것을 기억해야합니다). JSR 133에서 발생하기 전에 어떤 일이 발생하는지 살펴 보겠습니다.
• 스레드의 각 동작은 해당 스레드의 모든 후속 조치 전에 발생합니다. • 모니터에서 잠금 해제는 해당 모니터의 후속 잠금을 마치기 전에 발생합니다. • 휘발성 필드에 대한 글은 그 휘발성을 읽기 전에 발생합니다. • 스레드에서 시작 () 호출은 시작 스레드에서 조치를 취하기 전에 발생합니다. • 스레드의 모든 작업은 다른 스레드가 해당 스레드의 join ()에서 성공적으로 반환되기 전에 발생합니다. • 조치 A가 조치 B 전에 발생하고 B는 조치 C 전에 발생하면 A는 C 전에 발생합니다.
번역 :
• 이전 작업은 같은 스레드에서 발생합니다. (즉, 단일 스레드에서는 코드 순서대로 실행하는 것이 합법적입니다. 그러나 컴파일러와 프로세서는 단일 스레드 환경에서 실행 결과에 영향을 미치지 않고 재정렬을 할 수 있습니다. 즉, 규칙은 컴파일 재정렬 및 명령 재 분격을 보장 할 수 없습니다).
• 모니터에서 작동을 잠금 해제하면 후속 잠금 작업 이전에 발생합니다. (동기화 규칙)
• 후속 읽기 작업 전에 휘발성 변수에 대한 작업을 작성하십시오. (휘발성 규칙)
• 스레드의 시작 () 메소드는 스레드의 모든 후속 작업 전에 발생합니다. (스레드 시작 규칙)
• 다른 스레드 가이 스레드에서 가입하고 성공적인 작업을 반환하기 전에 스레드의 모든 작업이 발생합니다.
• A가 발생하면 B가 발생하면 B가 발생하면 C가 발생하면 C (전이) 이전에 발생합니다.
여기서 우리는 주로 휘발성 변수의 질서를 보장하기위한 규칙을 세 번째 규칙을 살펴 봅니다. "Java Concurrency Programming : Core Theory"기사는 재정렬이 컴파일러 재정 주문 및 프로세서 재정렬로 나뉘어져 있다고 언급했습니다. 휘발성 메모리 의미론을 구현하기 위해 JMM은이 두 가지 유형의 휘발성 변수의 재정렬을 제한합니다. 다음은 휘발성 변수에 대해 JMM이 지정한 재정렬 규칙 테이블입니다.
| 재주문 할 수 있습니다 | 두 번째 작전 | |||
| 첫 번째 작업 | 정상 하중 일반 상점 | 휘발성 부하 | 휘발성 상점 | |
| 정상 하중 일반 상점 | 아니요 | |||
| 휘발성 부하 | 아니요 | 아니요 | 아니요 | |
| 휘발성 상점 | 아니요 | 아니요 | ||
3. 메모리 장벽
휘발성 가시성을 구현하고 의미론에 따라 발생합니다. 기본 JVM은 "메모리 배리어"를 통해 수행됩니다. 메모리 펜스라고도하는 메모리 배리어는 메모리 작업에 대한 순차적 제한을 구현하는 데 사용되는 일련의 프로세서 지침입니다. 위의 규칙을 완료하는 데 필요한 메모리 장벽은 다음과 같습니다.
| 필요한 장벽 | 두 번째 작전 | |||
| 첫 번째 작업 | 정상 하중 | 일반 상점 | 휘발성 부하 | 휘발성 상점 |
| 정상 하중 | 로드 스토어 | |||
| 일반 상점 | Storestore | |||
| 휘발성 부하 | 로드 | 로드 스토어 | 로드 | 로드 스토어 |
| 휘발성 상점 | Storeload | Storestore | ||
(1)로드로드 장벽
실행 순서 : load1 -> loadload -> load2
Load2 및 후속로드 명령어가 데이터를로드하기 전에 LOAD1에 의해로드 된 데이터에 액세스 할 수 있는지 확인하십시오.
(2) Storestore 장벽
실행 순서 : Store1 -> Storestore -> Store2
Store1 작업의 데이터가 Store2 전에 다른 프로세서에 표시되고 후속 스토어 지침이 실행되도록하십시오.
(3)로드 스토어 장벽
실행 순서 : load1 -> loadstore -> store2
Store2 및 후속 상점 지침이 실행되기 전에 Load1에 의해로드 된 데이터에 액세스 할 수 있는지 확인하십시오.
(4) Storeload 장벽
실행 순서 : store1 -> storeload -> load2
로드 및 후속로드 지침을 읽기 전에 Store1의 데이터가 다른 프로세서에 표시되어 있는지 확인하십시오.
마지막으로 예제를 사용하여 JVM에 메모리 장벽이 어떻게 삽입되는지 설명 할 수 있습니다.
package com.paddx.test.concurrent; public class memorybarrier {int a, b; 휘발성 int v, u; void f () {int i, j; i = a; J = B; i = V; // loadload j = u; // loadStore a = i; b = J; // Storestore v = i; // Storestore u = j; // StorEload i = u; // loadload // loadstore j = b; a = i; }}4. 요약
전반적으로, 휘발성을 이해하는 것은 여전히 상대적으로 어렵다. 특히 이해하지 못하면 서두르지 않아도됩니다. 그것을 완전히 이해하려면 프로세스가 필요합니다. 또한 후속 기사에서 휘발성의 사용 시나리오를 여러 번 볼 수 있습니다. 여기서 나는 휘발성과 원래의 기본 지식에 대한 기본적인 이해가 있습니다. 일반적으로, 휘발성은 동시 프로그래밍의 최적화로, 일부 시나리오에서 동기화 될 수 있습니다. 그러나 휘발성은 동기화 된 위치를 완전히 대체 할 수 없습니다. 일부 특별한 시나리오에서만 휘발성을 적용 할 수 있습니다. 일반적으로 동시 환경에서 실 안전을 보장하기 위해 다음 두 가지 조건을 동시에 충족해야합니다.
(1) 변수에 대한 쓰기 작업은 현재 값에 의존하지 않습니다.
(2)이 변수는 다른 변수와 불변에 포함되지 않습니다.
Java Concurrent 프로그래밍에 관한 위의 기사 : 휘발성 및 원리 분석의 사용은 내가 공유하는 모든 내용입니다. 나는 당신이 당신에게 참조를 줄 수 있기를 바랍니다. 그리고 당신이 wulin.com을 더 지원할 수 있기를 바랍니다.