이 기사의 주요 내용은 Java 인터뷰의 일반적인 지식 요점 인 휘발성 키워드입니다. 이 기사는 휘발성 키워드의 모든 측면을 자세히 소개합니다. 이 기사를 읽은 후 휘발성 키워드의 관련 문제를 완벽하게 해결할 수 있기를 바랍니다.
Java 관련 면접에서 많은 면접관은 면접관의 Java 동시성에 대한 이해를 조사하는 것을 좋아합니다. 휘발성 키워드를 작은 진입 점으로 사용하면 JMI (Java Memory Model)와 Java 동시 프로그래밍의 일부 기능을 요청할 수 있습니다. 심층적으로 기본 JVM 구현 및 운영 체제 관련 지식을 조사 할 수도 있습니다. 가상의 인터뷰 과정을 수행하여 Volitile 키워드에 대한 심도있는 이해를 얻으십시오!
내가 이해하는 한, 휘발성에 의해 수정 된 공유 변수는 다음과 같은 두 가지 특성을 가지고 있습니다.
1. 가변 작업에 대한 다른 스레드의 메모리 가시성을 확인하십시오.
2. 명령 재정렬을 금지합니다
이것은 이야기해야 할 것이 많으므로 Java 메모리 모델로 시작하겠습니다. Java Virtual Machine 사양은 JMI (Java Memory Model)를 정의하여 다양한 하드웨어와 운영 체제 간의 메모리 액세스 차이를 차단하여 Java 프로그램이 다양한 플랫폼에서 일관된 메모리 액세스 효과를 달성 할 수 있도록합니다. 간단히 말해서, CPU는 지침을 매우 빠르게 실행하기 때문에 메모리 액세스 속도가 훨씬 느리고 차이는 크기가 아니기 때문에 프로세서에서 작업하는 큰 사람들은 CPU에 여러 층의 캐시를 추가했습니다. Java 메모리 모델에서 위의 최적화는 다시 추상화됩니다. JMM은 모든 변수가 위에서 언급 한 일반 메모리와 유사한 주 메모리에 있음을 규정하고 있으며 각 스레드에는 자체 작업 메모리가 포함되어 있습니다. 쉽게 이해하기 위해 CPU의 레지스터 또는 캐시로 간주 될 수 있습니다. 따라서 스레드 작업은 주로 작업 메모리를 기반으로합니다. 그들은 자신의 작업 메모리에만 액세스 할 수 있으며 퇴근 전후에 메인 메모리와 값을 다시 동기화해야합니다. 나는 내가 그렇게 말한 것을 모른다.
스레드를 실행할 때 변수의 값은 먼저 기본 메모리에서 읽은 다음 작업 메모리의 사본에로드 한 다음 실행을 위해 프로세서로 전달됩니다. 실행이 완료되면 작업 메모리의 사본에 값이 할당되고 작업 메모리의 값이 기본 메모리로 다시 전달되고 기본 메모리의 값이 업데이트됩니다. 작업 메모리와 메인 메모리의 사용이 더 빠르지 만 몇 가지 문제가 발생합니다. 예를 들어 다음 예를보십시오.
i = i + 1;
i의 초기 값이 0이라고 가정하면, 하나의 스레드 만 실행하면 결과는 확실히 1이됩니다. 두 스레드가 실행되면 결과가 2가 얻을 수 있습니까? 이것이 반드시 그런 것은 아닙니다. 이것은 다음과 같습니다.
스레드 1 : 메인 메모리에서 I = 0 i + 1 // i = 1 스레드 2 : 메인 메모리에서로드 I 메인 메모리에서로드 I는 메인 메모리로 다시 값을 쓰지 않았기 때문에 여전히 0 i + 1 // i = 1 스레드 1 : 메인 메모리 스레드 2 : 메인 메모리로 저장합니다.
두 스레드가 위의 실행 프로세스를 따르는 경우, I의 마지막 값은 실제로 1입니다. 마지막 쓰기가 느리면 i의 값을 다시 읽을 수 있다면 0 일 수 있습니다. 이는 캐시 불일치 문제입니다. 다음은 방금 요청한 질문을 언급하는 것입니다. JMM은 주로 동시성 프로세스에서 원자력, 가시성 및 순서의 세 가지 특성을 다루는 방법을 중심으로 설정됩니다. 이 세 가지 문제를 해결함으로써 캐시 불일치 문제를 해결할 수 있습니다. 휘발성은 가시성과 질서와 관련이 있습니다.
1. 원자력 : Java에서 기본 데이터 유형의 읽기 및 할당 작업은 원자 운영입니다. 소위 원자 연산은 이러한 작업이 무질서할 수 없으며 일정 기간 동안 완료되어야한다는 것을 의미합니다. 그렇지 않으면 실행되지 않습니다. 예를 들어:
i = 2; j = i; i ++; i = i+1;
위의 네 가지 작업 중 i = 2는 읽기 작업이며, 이는 원자 연산이어야합니다. j = 나는 그것이 원자 연산이라고 생각합니다. 실제로, 그것은 두 단계로 나뉩니다. 하나는 i의 값을 읽은 다음 값을 J에 할당하는 것입니다. 이것은 2 단계 작업입니다. 원자 연산이라고 할 수 없습니다. I ++ 및 i = i+ 1은 실제로 동일합니다. i의 값을 읽고 1을 추가하고 메인 메모리에 다시 씁니다. 그것은 3 단계 작업입니다. 따라서 위의 예에서, 마지막 값은 원자력을 만족시킬 수 없기 때문에 많은 상황을 가질 수 있습니다. 이런 식으로 간단한 독서 만 있습니다. 할당은 원자 연산이거나 수치 할당입니다. 변수를 사용하는 경우 변수 값을 읽기위한 추가 작업이 있습니다. 가상 머신 사양에 따라 64 비트 데이터 유형 (길고 이중)이 2 개의 작업에서 처리 될 수 있지만 최신 JDK 구현은 여전히 원자 운영을 구현합니다. JMM은 기본 원자력만을 구현합니다. 위의 I ++와 같은 작업은 전체 코드의 원자력을 보장하기 위해 동기화 및 잠금해야합니다. 스레드가 잠금을 풀기 전에 필연적으로 I의 값을 메인 메모리로 닦습니다. 2. 가시성 : 가시성에 대해 Java는 휘발성을 사용하여 가시성을 제공합니다. 변수가 휘발성에 의해 수정되면, 그 수정은 메인 메모리로 즉시 새로 고침됩니다. 다른 스레드가 변수를 읽어야하는 경우 새 값은 메모리에서 읽습니다. 이것은 일반 변수에 의해 보장되지 않습니다. 실제로 동기화 된 및 잠금은 가시성을 보장 할 수 있습니다. 스레드가 잠금을 풀기 전에 모든 공유 변수 값을 기본 메모리로 다시 플러시하지만 동기화 된 및 잠금은 더 비쌉니다. 3. JMM을 주문하면 컴파일러와 프로세서가 지침을 재정렬 할 수 있지만 AS-IF- 시리얼 시맨틱, 즉 프로그램의 실행 결과가 변경 될 수 없는지에 관계없이 규정됩니다. 예를 들어, 다음 프로그램 세그먼트 :
이중 PI = 3.14; // adouble r = 1; // bdouble s = pi * r * r; // c
위의 진술은 A-> B-> C로 실행될 수 있으며 결과는 3.14이지만 B-> A-> C의 순서대로 실행할 수도 있습니다. A와 B는 두 개의 독립적 인 진술이기 때문에 C는 A와 B에 의존하고 A와 B는 재주문 할 수 있지만 A와 B에서 C를 먼저 순위를 매길 수는 없습니다. JMM은 재정렬이 단일 스레드의 실행에 영향을 미치지 않도록하지만 멀티 스레딩에서 문제가 발생하기 쉽습니다. 예를 들어, 다음과 같은 코드 :
int a = 0; bool flag = false; public void write () {a = 2; // 1 flag = true; // 2} public void multiply () {if (flag) {// 3 int ret = a * a; // 4}}두 스레드가 위의 코드 세그먼트를 실행하면 스레드 1은 먼저 쓰기를 실행 한 다음 스레드 2를 실행 한 다음 곱하기를 실행하면 Ret의 값이 4 여야합니까? 결과가 반드시 : 반드시 :
그림과 같이, 쓰기 방법의 1과 2는 재정렬된다. 스레드 1은 먼저 플래그를 true에 할당 한 다음 스레드 2로 실행하고 RET가 결과를 직접 계산 한 다음 스레드 1로 실행합니다.이 시점에서 A는 2에 할당됩니다. 현재 휘발성 키워드를 플래그에 추가하고 재정렬을 금지하여 프로그램의 순서를 보장 할 수 있으며, 헤비급 동기화 된 및 잠금 장치를 사용하여 질서를 보장 할 수도 있습니다. 해당 영역의 코드가 한 번에 실행되도록 할 수 있습니다. 또한 JMM은 선천적 질서, 즉 어떠한 수단도없이 보장 할 수있는 순서가 있으며, 이는 일반적으로 발생하기 전에 발생합니다. << JSR-133 : Java 메모리 모델 및 스레드 사양 >> 앞서 발생할 수 있습니다. 1. 프로그램 시퀀스 규칙 : 스레드의 각 작업에 대해 스레드의 후속 작업에 발생하는 경우가 발생합니다. 스레드 잠금 규칙 : 스레드 잠금을 해제,이 스레드의 후속 잠금에 사용되는 경우 : 휘발성이 사용됩니다. 휘발성 도메인 4. 트랜스 스트리트 : A가 발생하는 경우 A가 발생하는 경우 A가 발생하면 A가 발생할 경우 A가 발생합니다. C 5.Start () 규칙 : 스레드 A가 수행되는 경우 ThreadB_Start () (시작 스레드 B)를 수행하면 STREINDB_START ()가 B 6.Join ()의 임의의 작업이 발생하면 실행자가 발생하는 경우 () 스레드 A의 스레드 A에서 성공적으로 반환됩니다. 스레드. interrupted () 메소드를 사용하여 중단이 있는지 여부를 감지 할 수 있습니다. 8. finalize () 원리 : 객체의 초기화 완료는 Finalize () 메소드가 시작될 때 먼저 발생합니다. 프로그램 시퀀스 규칙의 첫 번째 규칙에 따르면 스레드에서는 모든 작업이 순서대로 있지만 실행 결과가 동일하다면 JMM에서는 재정렬이 허용됩니다. 여기서는 발생의 초점은 단일 스레드 실행 결과의 정확성이지만 멀티 스레딩에 대해서도 마찬가지임을 보장 할 수는 없습니다. 규칙 2 모니터 규칙은 실제로 이해하기 쉽습니다. 잠금 장치를 추가하기 전에 잠금을 계속 추가 할 수 있습니다. 규칙 3은 해당 휘발성에 적용됩니다. 한 스레드가 먼저 변수를 쓰고 다른 스레드가 읽는 경우, 쓰기 작업은 읽기 작업 전에 있어야합니다. 네 번째 규칙은 이전에 발생하는 과도 성입니다. 나는 다음 몇 가지에 대한 자세한 내용을 보지 않을 것입니다.
그런 다음 휘발성 변수 규칙을 다시 언급해야합니다. 휘발성 도메인을 작성하고 나중에이 휘발성 도메인을 읽기 전에 발생합니다. 이것을 다시 꺼내겠습니다. 실제로 변수가 휘발성으로 선언되면 변수를 읽을 때 항상 최신 값을 읽을 수 있습니다. 여기서 최신 값은 다른 스레드가 변수를 작성하더라도 즉시 기본 메모리로 업데이트 될 것임을 의미합니다. 메인 메모리에서 새로 쓰여진 값을 읽을 수도 있습니다. 다시 말해, 휘발성 키워드는 가시성과 질서를 보장 할 수 있습니다. 위의 코드를 예로 들어 봅시다.
int a = 0; bool flag = false; public void write () {a = 2; // 1 flag = true; // 2} public void multiply () {if (flag) {// 3 int ret = a * a; // 4}}이 코드는 1과 2가 재정렬되지 않더라도 재정렬로 인해 문제가되지 않습니다. 3도 매끄럽게 실행되지 않습니다. 스레드 1이 먼저 쓰기 작업을 실행하고 스레드 2가 곱셈 작업을 수행한다고 가정합니다. 스레드 1은 작업 메모리에서 플래그를 1로 할당하므로 즉시 메인 메모리에 다시 기록되지 않을 수 있습니다. 따라서 스레드 2가 실행되면 곱하기 메인 메모리에서 플래그 값을 읽습니다. 다음으로 변경 한 경우 :
int a = 0; volatile bool flag = false; public void write () {a = 2; // 1 flag = true; // 2} public void multiply () {if (flag) {// 3 int ret = a * a; // 4}}그런 다음 스레드 1은 먼저 쓰기를 실행하고 스레드 2는 곱하기를 실행합니다. 이전의 원칙에 따라이 과정은 다음과 같은 세 가지 유형의 규칙을 충족시킬 것입니다. 프로그램 주문 규칙 : 1 이전에 발생합니다. 2; 3 이전에 발생합니다. (휘발성 명령 재정렬 제한을 제한하므로 1은 2 전에 실행됩니다. 2) 휘발성 규칙 : 2 전이 3 가지 전이 규칙 : 1 휘발성 변수를 작성할 때 JMM은 스레드에 해당하는 로컬 메모리의 공유 변수를 기본 메모리에 해당합니다. 휘발성 변수를 읽을 때 JMM은 스레드에 해당하는 로컬 메모리를 무효화하도록 설정하고 스레드는 다음 주 메모리에서 공유 변수를 읽습니다.
우선, 내 대답은 원자력을 보장 할 수 없다는 것입니다. 보장되면 단일 휘발 변수의 판독/쓰기에만 원자력이지만 다음 예와 같은 휘발성 ++와 같은 복합 작업과 관련이 없습니다.
공개 클래스 테스트 {공개 휘발성 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 (); }; }.시작(); } while (ride.ActiveCount ()> 1) // 이전 스레드가 thread.yield ()를 완료했는지 확인하십시오. System.out.println (test.inc); }논리적으로 말하면, 결과는 10,000이지만 실행할 때는 10,000 미만의 값 일 수 있습니다. 어떤 사람들은 휘발성이 가시성을 보장하지 않는다고 말할 수 있습니다. 한 스레드는 Inc의 Inc로 수정을보아야하고 다른 스레드는 즉시 볼 수 있어야합니다! 그러나 여기서 Operation Inc ++는 Inc의 값을 읽고, 자체적으로 증가시킨 다음 주 메모리에 다시 작성하는 등 복합 작업이 있습니다. 스레드 A가 INC의 값을 10으로 읽고 변수가 수정되지 않고 휘발성 규칙을 트리거 할 수 없기 때문에이 시점에서 차단됩니다. 스레드 B는 또한 현재 Inc의 값을 읽습니다. 메인 메모리의 Inc 값은 여전히 10이며 자동으로 증가한 다음 메인 메모리 (11)로 다시 기록됩니다.이 시점에서 실행 대기 스레드 A의 차례입니다. 10은 작업 메모리에 저장되므로 계속해서 스스로를 증가시키고 메인 메모리에 기록합니다. 11이 다시 작성되었습니다. 따라서 두 스레드가 yally ()를 두 번 실행했지만 한 번만 추가했습니다. 어떤 사람들은 휘발성이 캐시 라인을 무효화하지 않는다고 말합니까? 그러나 스레드 A가 읽고 스레드 B를 읽고 작업을 수행하기 전에 Inc 값이 수정되지 않으므로 스레드 B가 읽을 때 여전히 10을 읽습니다. 일부 사람들은 스레드 B가 11을 기본 메모리로 다시 쓰면 스레드 A의 캐시 라인을 무효화하지 않을 것이라고 말합니다. 그러나 스레드 A는 이미 읽기 작업을 수행했습니다. 읽기 작업이 완료되고 캐시 라인이 잘못된 경우에만 기본 메모리 값을 읽습니다. 따라서 스레드 A는 계속해서 자기 증가를 할 수 있습니다. 요약하면, 이러한 종류의 복합 작업에서 원자 기능을 유지할 수 없습니다. 그러나 위의 플래그 값을 설정하는 예에서 플래그의 읽기/쓰기 작동은 단일 단계이므로 원자력을 보장 할 수 있습니다. 원자력을 보장하기 위해 동시 패킷에서 동기화, 잠금 및 원자 원자력 작동 클래스, 즉 자체 증가 (1 조작 추가), 자체 추적 (1 조작 감소), 추가 작업 (추가) 및 하위 추적 작업 (1 숫자를 첨가)하여 이러한 작업이 원자 운영을 보장 할 수 있습니다.
휘발성 키워드가없는 휘발성 키워드와 코드로 어셈블리 코드를 생성하면 휘발성 키워드가있는 코드에 추가 잠금 접두사 명령이 있음을 알 수 있습니다. 잠금 접두사 명령은 실제로 메모리 장벽과 동일합니다. 메모리 배리어는 다음과 같은 기능을 제공합니다. 1. 재정렬 일 때, 메모리 배리어 2 이전에 다음 지침을 위치로 재정렬 할 수 없습니다.
1. 상태 수량 마크 위의 깃발과 마찬가지로 다시 언급하겠습니다.
int a = 0; volatile bool flag = false; public void write () {a = 2; // 1 flag = true; // 2} public void multiply () {if (flag) {// 3 int ret = a * a; // 4}}이 읽기 및 쓰기 작업은 휘발성으로 표시된 변수에 대한 작업을 통해 수정이 즉시 스레드에 표시되도록 보장 할 수 있습니다. 동기화 된 것과 비교하여 Lock은 특정 효율이 향상됩니다. 2. 싱글 톤 모드 구현, 일반적인 이중 체크 잠금 (DCL)
클래스 싱글 톤 {개인 휘발성 정적 싱글 톤 인스턴스 = null; private singleton () {} public static singleton getinstance () {if (instance == null) {synchronized (singleton.class) {if (instance == null) instance = new Singleton (); }} return instance; }}이것은 게으른 싱글 톤 패턴이며, 객체는 사용될 때만 생성되며 초기화 작업에 대한 리 주문 지침을 피하기 위해 휘발성이 인스턴스에 추가됩니다.
위는이 기사의 전체 내용입니다. Java 면접관이 좋아하는 휘발성 키워드를 설명하는 것입니다. 모든 사람에게 도움이되기를 바랍니다. 관심있는 친구는이 사이트의 다른 관련 주제를 계속 참조 할 수 있습니다. 단점이 있으면 메시지를 남겨 두십시오. 이 사이트를 지원해 주신 친구들에게 감사드립니다!