머리말
때로는 동기화를 사용하여 하나 또는 두 개의 인스턴스 필드를 읽고 쓰면 너무 비싸게 보입니다. 휘발성 키워드는 인스턴스 필드에 대한 동기식 액세스를위한 잠금 장치 메커니즘을 제공합니다. 도메인이 휘발성으로 선언되면 컴파일러와 가상 시스템은 도메인이 다른 스레드에 의해 동시에 업데이트 될 수 있음을 알고 있습니다. 휘발성 키워드에 대해 이야기하기 전에 메모리 모델의 관련 개념과 동시 프로그래밍의 세 가지 특성, 즉 원자, 가시성 및 질서를 이해해야합니다.
1. 원자력, 가시성 및 질서를 가진 Java 메모리 모델
Java 메모리 모델은 모든 변수가 기본 메모리에 존재한다고 규정하고 각 스레드에는 자체 작업 메모리가 있습니다. 변수의 스레드의 모든 작업은 작업 메모리에서 수행해야하며 기본 메모리에서 직접 작동 할 수 없습니다. 각 스레드는 다른 스레드의 작업 메모리에 액세스 할 수 없습니다.
Java에서는 다음 진술을 실행합니다.
int i = 3;
실행 스레드는 먼저 변수 i가 자체 작업 스레드에있는 캐시 라인을 할당 한 다음 주 메모리에 작성해야합니다. 메인 메모리에 값 3을 직접 작성하는 대신.
그렇다면 Java 언어 자체가 원자력, 가시성 및 질서를 제공하는 것은 무엇입니까?
원자력
기본 데이터 유형의 변수의 읽기 및 할당 작업은 원자 연산입니다. 즉, 이러한 작업은 중단 및 실행되지 않거나 실행할 수 없습니다.
다음 코드를 살펴 보겠습니다.
x = 10; // 문 1y = x; // 문 2x ++; // 문 3x = x + 1; // 문 4
진술 1만이 원자 연산이며, 다른 세 가지 진술 중 어느 것도 원자 연산이 아닙니다.
진술 2에는 실제로 2 개의 작업이 포함됩니다. 먼저 x의 값을 읽은 다음 x의 값을 작업 메모리에 기록해야합니다. X의 값을 읽고 X의 값을 작업 메모리에 쓰는 두 가지 작업은 원자 연산이지만, 원자 연산은 아닙니다.
마찬가지로 X ++ 및 X = X+1에는 3 개의 작업이 포함됩니다. X의 값을 읽고 1 추가 작업을 수행하고 새 값을 작성하십시오.
다시 말해서, 간단한 판독 및 할당 만 (그리고 숫자는 변수에 할당되어야하며 변수 간의 상호 할당은 원자 연산이 아닙니다)는 원자 연산입니다.
java.util.concurrent.atomic 패키지에는 다른 작업의 원자력을 보장하기 위해 매우 효율적인 기계 수준 지침 (잠금이 아닌)을 사용하는 많은 클래스가 있습니다. 예를 들어, Atomicinteger 클래스는 메소드 증분 및 감소 및 get을 제공하여 각각 원자 방식으로 정수를 증가시키고 감소시킵니다. Atomicinteger 클래스는 동기화없이 공유 카운터로 안전하게 사용할 수 있습니다.
또한이 패키지에는 동시 도구 만 개발하는 시스템 프로그래머에 대한 Atomicboolean, Atomiclong 및 Atomicreference와 같은 원자 클래스가 포함되어 있으며 응용 프로그램 프로그래머는 이러한 클래스를 사용해서는 안됩니다.
시계
가시성은 스레드 간의 가시성을 말하며 한 스레드의 수정 된 상태는 다른 스레드로 보입니다. 이것이 스레드 수정의 결과입니다. 다른 스레드가 즉시 나타납니다.
휘발성에 의해 공유 변수가 수정되면 수정 된 값이 즉시 기본 메모리로 업데이트되도록하므로 다른 스레드로 표시됩니다. 다른 스레드를 읽어야 할 때 메모리의 새로운 값을 읽습니다.
그러나 일반 공유 변수는 수정 된 후 정상 공유 변수가 기본 메모리에 기록 된시기가 확실하지 않기 때문에 가시성을 보장 할 수 없습니다. 다른 스레드가 읽을 때 원래의 오래된 값은 여전히 메모리에있을 수 있으므로 가시성을 보장 할 수 없습니다.
주문
Java 메모리 모델에서 컴파일러와 프로세서는 지침을 재정렬 할 수 있지만 재정렬 프로세스는 단일 스레드 프로그램의 실행에 영향을 미치지 않지만 멀티 스레드 동시 실행의 정확성에 영향을 미칩니다.
휘발성 키워드를 사용하여 특정 "OrderLine"을 보장 할 수 있습니다. 또한 동기화 및 잠금을 사용하여 순서를 보장 할 수 있습니다. 분명히 동기화 된 및 잠금은 매 순간에 동기화 코드를 실행하는 스레드가 있는지 확인합니다. 이는 스레드가 순서대로 동기화 코드를 실행하도록하는 것과 동일하며, 이는 자연스럽게 순서를 보장합니다.
2. 휘발성 키워드
공유 변수 (클래스 멤버 변수, 클래스 정적 멤버 변수)가 휘발성에 의해 수정되면 두 개의 의미론이 있습니다.
먼저 코드를 살펴 보겠습니다. 스레드 1이 먼저 실행되고 스레드 2가 나중에 실행 된 경우 :
// 스레드 1boolean spop = false; while (! stop) {dosomething ();} // thread 2stop = true; 많은 사람들이 스레드를 방해 할 때이 마크 업 방법을 사용할 수 있습니다. 그러나 실제로이 코드가 완전히 올바르게 실행됩니까? 스레드가 중단됩니까? 반드시 그런 것은 아닙니다. 아마도 대부분의 경우,이 코드는 스레드를 방해 할 수 있지만 스레드가 중단되지 않을 수도 있습니다 (이 가능성은 매우 작지만 일단 발생하면 죽은 루프가 발생합니다).
스레드가 방해하지 않는 이유는 무엇입니까? 각 스레드에는 실행중인 프로세스 중에 자체 작업 메모리가 있습니다. 스레드 1이 실행될 때 정지 변수의 값을 복사하여 자체 작업 메모리에 넣습니다. 그런 다음 스레드 2가 정지 변수의 값을 변경하지만 메인 메모리에 쓸 시간이 없으면 스레드 2는 다른 작업을 수행하면 스레드 2의 스톱 변수 변경에 대해 알지 못하므로 계속 루프됩니다.
그러나 휘발성으로 수정 한 후에는 다르게됩니다.
휘발성이 원자력을 보장합니까?
휘발성 키워드는 운영의 가시성을 보장하지만 변수의 작업이 원자인지 확인할 수 있다는 것을 알고 있습니까?
공개 클래스 테스트 {공개 휘발성 int Inc = 0; 공개 무효 증가 () {inc ++; } public static void main (String [] args) {최종 테스트 = new test (); for (int i = 0; i <10; i ++) {new Thread () {public void run () {for (int j = 0; j <1000; j ++) test.increase (); }; }.시작(); } // 이전 스레드가 실행을 완료했는지 확인하십시오. System.out.println (test.inc); }} 이 코드의 결과는 실행할 때마다 일치하지 않습니다. 10,000보다 적습니다. 앞에서 언급했듯이 자동 증가 작업은 원자가 아닙니다. 여기에는 변수의 원래 값을 읽고 추가 1 작업을 수행하고 작업 메모리에 쓰기가 포함됩니다. 즉, 자체 증가 작업의 세 가지 하위 작동은 별도로 실행될 수 있습니다.
변수 Inc의 값이 특정 시간에 10 인 경우 스레드 1은 변수에 대한 자체 개수 작업을 수행하고 스레드 1은 먼저 Variable Inc의 원래 값을 읽은 다음 스레드 1이 차단됩니다. 그런 다음 스레드 2는 변수에서 자체 점수 작업을 수행하고 Thread 2는 변수 inc의 원래 값을 읽습니다. 스레드 1은 변수 Inc에서만 읽기 작업을 수행하고 변수를 수정하지 않으므로 스레드 2의 캐시 변수 Inc의 캐시 라인이 작업 메모리에서 무효화되지 않으므로 스레드 2는 주 메모리로 직접 이동하여 Inc의 값을 읽습니다. INC의 값이 10이라는 것이 발견되면 1의 증가를 수행하고 작업 메모리에 11을 기록하고 마지막으로 메인 메모리에 씁니다. 그런 다음 스레드 1은 추가 작업을 수행합니다. INC의 값이 읽히기 때문에 현재 스레드 1의 Inc 값은 현재 10이므로 스레드 1이 추가 된 후 Inc의 값은 11이고, 11은 메모리를 작성하고 마지막으로 메인 메모리에 씁니다. 그런 다음 두 스레드가 자체 점수 작업을 수행 한 후 INC는 1 만 증가합니다.
자동화 조작은 원자 작동이 아니며 변수의 모든 작업이 원자임을 보장 할 수 없습니다.
변동성이 질서를 보장 할 수 있습니까?
앞에서 언급했듯이 휘발성 키워드는 명령어 재정렬을 금지 할 수 있으므로 휘발성은 어느 정도 순서를 보장 할 수 있습니다.
휘발성 키워드의 재정비를 금지하는 두 가지 의미가 있습니다.
3. 휘발성 키워드를 올바르게 사용하십시오
동기화 된 키워드는 여러 스레드가 동시에 코드를 실행하는 것을 방지하여 프로그램 실행 효율에 큰 영향을 미칩니다. 휘발성 키워드의 성능은 경우에 따라 동기화 된 것보다 낫습니다. 그러나 휘발성 키워드는 동기화 된 키워드를 대체 할 수 없다는 점에 유의해야합니다. 휘발성 키워드는 작업의 원자력을 보장 할 수 없기 때문입니다. 일반적으로 휘발성을 사용할 때 다음 두 가지 조건을 충족해야합니다.
첫 번째 조건은 자체 증가 및 자체 추출과 같은 작업이 될 수 없다는 것입니다. 위에서 언급했듯이 휘발성은 원자력을 보장하지 않습니다.
이것의 예를 들어 봅시다. 그것은 변하지 않는 것을 포함합니다. 하한은 항상 상한보다 작거나 동일합니다.
공개 클래스 번호 레인지 {개인 휘발성 int 하단, 상단; public int getLower () {return lower; } public int getUpper () {return thep; } public void setLower (int value) {if (value> upper) 새로운 불법 불법 행위 (...); 낮은 = 값; } public void setupper (int value) {if (value <lower) throw new neveralargumentexception (...); 상단 = 값; }} 이러한 방식으로 스코어 된 상태 변수를 제한하므로 하부 및 상단 필드를 휘발성 유형으로 정의하면 클래스의 스레드 안전성을 완전히 구현하지 않으므로 동기화가 필요합니다. 그렇지 않으면 두 스레드가 SetLower 및 Setupper를 동시에 일치하지 않는 값으로 실행하는 경우 범위가 일치하지 않습니다. 예를 들어, 초기 상태가 (0, 5)이고 동시에 A SetLower (4) 및 스레드 B 호출 Setupper (3)를 스레드하면이 두 작업에 의해 크로스 저장된 값이 조건을 충족시키지 못하면 두 스레드가 불변을 보호하기 위해 점검을 전달하여 마지막 범위 값이 (4, 3)이라는 것이 분명합니다.
실제로, 그것은 휘발성을 사용하도록 작업의 원자력을 보장하는 것입니다. 휘발성 사용을위한 두 가지 주요 시나리오가 있습니다.
상태 플래그
휘발성 부울 셧다운; } public void dowork () {while (! shutdownRequested) {// do do}}종료 변수의 가시성을 올바르게 구현하기 위해 루프 외부 (즉, 다른 스레드에서 종료) 메소드가 루프 외부에서 호출 될 수 있으므로 일부 종류의 동기화가 수행되어야합니다. 그러나 동기화 된 블록으로 루프를 작성하는 것은 휘발성 상태 플래그로 작성하는 것보다 훨씬 더 번거 롭습니다. 휘발성은 인코딩을 단순화하고 상태 플래그는 프로그램의 다른 상태에 의존하지 않기 때문에 여기서 휘발성에 매우 적합합니다.
이중 확인 모드 (DCL)
공개 클래스 싱글 톤 {개인 휘발성 정적 싱글 톤 인스턴스 = null; public static singleton getinstance () {if (instance == null) {synchronized (this) {if (instance == null) {instance = new Singleton (); }} return instance; }} 여기에 휘발성을 사용하면 성능에 어느 정도 영향을 미치지 만 프로그램의 정확성을 고려할 때 여전히이 성능을 희생하는 것이 좋습니다.
DCL의 장점은 자원 활용이 높다는 것입니다. 싱글 톤 객체는 GetInstance가 처음으로 실행될 때만 인스턴스화되며, 이는 매우 효율적입니다. 단점은 처음으로 로딩 할 때 반응이 약간 느려지고 동시성 높은 환경에 특정 결함이 있지만 발생 가능성은 매우 작다는 것입니다.
DCL은 자원 소비, 불필요한 동기화, 스레드 안전 등의 문제를 해결하지만 어느 정도는 여전히 고장 문제, 즉 DCL 고장이 있습니다. "Java Concurrency 프로그래밍 실습"이라는 책에서 다음 코드 (정적 내부 클래스 싱글 톤 패턴)를 사용하여 DCL을 대체 할 것을 권장합니다.
공개 클래스 싱글 턴 {private singleton () {} public static singleton getinstance () {return singletonholder.sinstance; } 개인 정적 클래스 싱글 톤 홀더 {개인 정적 최종 싱글 턴 Sinstance = New Singleton (); }} 이중 점검에 대해 볼 수 있습니다
4. 요약
잠금과 비교할 때 휘발성 변수는 매우 간단하지만 동시에 잠금보다 성능과 확장 성을 더 잘 제공하는 매우 연약한 동기화 메커니즘입니다. 다른 변수와 자체 이전 값과 진정으로 독립적 인 휘발성의 사용 조건을 엄격하게 따르면 동기화 대신 휘발성을 사용하여 경우에 따라 코드를 단순화 할 수 있습니다. 그러나 휘발성을 사용하는 코드는 종종 잠금을 사용하는 코드보다 오류가 발생하기 쉽습니다. 이 기사는 동기화 대신 휘발성을 사용할 수있는 두 가지 가장 일반적인 사용 사례를 소개합니다. 다른 경우에는 동기화 된 것을 더 잘 사용하는 것이 좋습니다.
위의 내용은이 기사에 관한 모든 것입니다. 모든 사람의 학습에 도움이되기를 바랍니다.