휘발성 변수는 스레드 가시성을 제공하며 스레드 안전성과 원자력을 보장하지 않습니다.
스레드 가시성이란 무엇입니까 :
자물쇠는 상호 배제와 가시성의 두 가지 주요 기능을 제공합니다. 상호 제외는 하나의 스레드 만 한 번에 특정 잠금을 보유 할 수 있음을 의미 하므로이 기능을 사용하여 공유 데이터에 대한 조정 된 액세스 프로토콜을 구현하여 한 번만 공유 데이터를 한 번에 사용할 수 있습니다. 가시성은 조금 더 복잡하며, 잠금 장치를 출시하기 전에 공유 데이터의 변경이 다른 스레드로 표시되도록해야합니다. 그 후에 잠금을 획득하는 다른 스레드로 표시되도록해야합니다.
휘발성의 의미를 참조하십시오.
휘발성은 동기화 된 약한 구현과 동일하므로 휘발성은 동기화 된 의미를 구현하지만 잠금 메커니즘이 없음을 의미합니다. 휘발성 필드에 대한 업데이트가 예측 가능한 방식으로 다른 스레드에 알려줍니다.
휘발성은 다음과 같은 의미를 포함합니다.
(1) Java Storage 모델은 Valatile Intructions의 작업을 재정렬하지 않습니다. 이는 휘발성 변수에 대한 작업이 지침이 나타나는 순서대로 실행되도록합니다.
(2) 휘발성 변수는 레지스터에 캐시되지 않습니다 (스레드 만 표시됩니다) 또는 CPU에 보이지 않는 다른 장소. 휘발성 변수의 결과는 항상 메인 메모리에서 매번 읽습니다. 다시 말해, 휘발성 변수의 수정을 위해 다른 스레드는 항상 볼 수 있으며 스레드 스택 내부에 변수를 사용하지 않습니다. 즉, 법률에서, 법률에서, Valatile 변수를 작성한 후, 후속 읽기 작업은이 쓰기 작업의 결과를 볼 수 있습니다.
휘발 변수의 특성은 양호하지만 휘발성은 스레드 안전을 보장 할 수 없습니다. 즉, 휘발성 필드의 작동은 원자가 아닙니다. 휘발성 변수는 가시성 만 보장 할 수 있습니다 (다른 스레드는 한 스레드가 수정 된 후이 변경을 본 후 결과를 이해할 수 있습니다). 원자력을 보장하기 위해 지금까지만 잠글 수 있습니다!
휘발성 사용 원리 :
휘발성 변수를 적용하기위한 세 가지 원칙 :
(1) 쓰기 변수는이 변수의 값에 의존하지 않거나 하나의 스레드만이 변수를 수정합니다.
(2) 변수의 상태는 다른 변수와 함께 불변의 제약 조건에 참여할 필요가 없습니다.
(3) 액세스 변수를 잠글 필요는 없습니다
실제로, 이러한 조건은 휘발성 변수에 쓸 수있는 이러한 유효한 값이 변수의 현재 상태를 포함하여 모든 프로그램의 상태와 무관하다는 것을 나타냅니다.
첫 번째 조건 제한은 휘발성 변수가 스레드-안전 카운터로 사용되는 것을 방지합니다. 증분 조작 (x ++)은 별도의 작업처럼 보이지만 실제로 원자 적으로 수행 해야하는 일련의 read-modify-write 작업으로 구성된 결합 된 작업이며 휘발성은 필요한 원자 특성을 제공 할 수 없습니다. 올바른 작업을 구현하려면 작업 중 X 값을 일정하게 유지해야하며 휘발 변수에는 불가능합니다. (그러나 값이 단일 스레드에서만 작성되도록 조정되면 첫 번째 조건을 무시할 수 있습니다.)
대부분의 프로그래밍 상황은이 세 가지 조건 중 하나와 충돌하여 휘발성 변수가 동기화 된 스레드 안전에 보편적으로 적용되지 않습니다. 목록 1은 비 스레드-안전 숫자 범위 클래스를 보여줍니다. 불변량이 포함되어 있습니다. 하한은 항상 상한보다 작거나 동일합니다.
휘발성을 올바르게 사용하십시오.
모드 #1 : 상태 플래그
아마도 휘발성 변수를 구현하는 사양은 단순히 부울 상태 플래그를 사용하여 초기화 완료 또는 다운 타임 요청과 같은 중요한 일회성 이벤트가 발생했음을 나타냅니다.
많은 응용 프로그램에는 목록 2에 표시된 것처럼 "프로그램이 중지되지 않을 때 일부 작업 실행"형태의 제어 구조가 포함됩니다.
Listing 2. 휘발성 변수를 상태 플래그로 사용하십시오.
휘발성 부울 셧다운; … public void shutdown () {shutdownRequested = true; } public void dowork () {while (! shutdownRequested) {// do do}}종료 변수의 가시성을 올바르게 구현하기 위해 루프 외부 (즉, 다른 스레드에서 종료) 메소드가 루프 외부에서 호출 될 가능성이 높습니다. (JMX 리스너, GUI 이벤트 스레드의 작동 리스너, RMI, 웹 서비스 등을 통해 호출 될 수 있습니다). 그러나 동기화 된 블록으로 루프를 작성하는 것은 Listing 2에 표시된 휘발성 상태 플래그를 쓰는 것보다 훨씬 더 번거 롭습니다. 휘발성이 인코딩을 단순화하고 상태 플래그는 프로그램 내의 다른 상태에 의존하지 않기 때문에 여기서 휘발성에 매우 적합합니다.
이러한 유형의 상태 태그의 일반적인 특징은 일반적으로 하나의 상태 전환만이 있다는 것입니다. 종료 요청 플래그는 False에서 True로 변환되며 프로그램은 중지됩니다. 이 패턴은 앞뒤로 전환의 상태 플래그로 확장 될 수 있지만 전환 기간이 눈에 띄지 않는 경우에만 확장 될 수 있습니다 (False에서 True, True, False로). 또한, 원자 변수와 같은 일부 원자 상태 변환 메커니즘이 필요합니다.
모드 #2 : 일회성 안전 출판
동기화가 부족하면 성취 할 수없는 가시성이 이어질 수 있으며, 이는 원시적 값 대신 객체 참조를 작성 해야하는시기를 결정하기가 더 어려워집니다. 동기화가없는 경우, 객체 (다른 스레드가 작성)에 의해 참조 된 업데이트 된 값이 발생할 수 있으며 해당 객체의 기존 값이 동시에 존재합니다. (이것은 객체 참조가 동기화없이 읽히는 유명한 이중 점검 잠금 문제의 근본 원인으로, 업데이트 된 참조가 표시 될 수 있지만 그 참조를 통해 불완전하게 구성된 객체를 볼 수있는 문제가 발생합니다).
객체의 안전한 게시를 구현하는 한 가지 기술은 객체 참조를 휘발성 유형으로 정의하는 것입니다. Listing 3은 Background 스레드가 시작 중에 데이터베이스에서 일부 데이터를로드하는 예를 보여줍니다. 다른 코드는이 데이터를 사용하기 전에 게시되었는지 확인하십시오.
목록 3. 일회성 안전 릴리스에 휘발성 변수 사용
공개 클래스 배경 FloObleloader {공개 휘발성 플루블 theflooble; public void initinbackground () {// 많은 일을 수행하십시오. // 이것은 theflooble}}에 유일한 글입니다}} public class someotherclass {public void dowork () {while (true) {// 몇 가지 일을합니다… // flooble을 사용하지만 (floobleloader.theflooble! = null) dosomething (floobleloader.theflooble); }}}플루 블 레퍼런스가 휘발성 유형이 아닌 경우, Dowork ()의 코드는 플루블을 회상 할 때 불완전하게 구성된 flooble을 얻게됩니다.
이 패턴에 필요한 조건은 게시 된 객체가 스레드-안전 또는 유효한 불변의 객체 여야한다는 것입니다 (효과적인 불변은 객체의 상태가 출판 후에는 수정되지 않음을 의미합니다). 유형 휘발성 참조 객체의 간행물 형식의 가시성을 보장하지만 출판 후 객체의 상태가 변경되면 추가 동기화가 필요합니다.
패턴 #3 : 독립적 인 관찰
휘발성을 안전하게 사용하는 또 다른 간단한 모드는 프로그램의 내부 사용을 위해 정기적으로 관찰을 "릴리스"하는 것입니다. 예를 들어, 주변 온도를 감지 할 수있는 주변 센서가 있다고 가정합니다. 배경 스레드는 몇 초마다 센서를 읽고 현재 문서가 포함 된 휘발성 변수를 업데이트 할 수 있습니다. 그런 다음 다른 스레드 가이 변수를 읽으므로 언제든지 최신 온도 값을 볼 수 있습니다.
이 모드를 사용하는 또 다른 응용 프로그램은 프로그램의 통계를 수집하는 것입니다. 목록 4는 인증 메커니즘이 마지막으로 로그인 한 사용자의 이름을 기억하는 방법을 보여줍니다. 프로그램의 다른 부분에 대한 값을 게시하려면 LastUser 참조를 반복하십시오.
목록 4. 다중 독립적 인 관찰을 게시하기 위해 휘발성 변수 사용
공개 클래스 userManager {public volatile String lastUser; public boolean authenticate (문자열 사용자, 문자열 암호) {boolean valid = passwordisvalid (user, password); if (유효) {user u = new user (); ActiveUsers.add (u); lastuser = 사용자; } 반환 유효; }}이 모드는 이전 모드의 확장입니다. 프로그램의 다른 곳에서 사용하기위한 특정 가치를 게시하지만 일회성 이벤트를 게시하는 것과는 달리 일련의 독립적 인 이벤트입니다. 이 패턴은 게시 된 값이 유효하고 불변성이어야합니다. 즉, 출판 후에는 값의 상태가 변경되지 않습니다. 이 값을 사용하는 코드는 언제든지 값이 변경 될 수 있음을 분명히해야합니다.
모드 #4 : "휘발성 Bean"모드
휘발성 Bean 패턴은 Javabeans를 "명예 구조"로 사용하는 프레임 워크에 적합합니다. 휘발성 Bean 패턴에서 Javabeans는 독립적 인 특성 및/또는 세터 방법을 갖는 컨테이너 세트로 사용됩니다. 휘발성 Bean 패턴의 기본 원리는 많은 프레임 워크가 휘발성 데이터 보유자 (예 : httpsession)를위한 컨테이너를 제공하지만,이 컨테이너에 배치 된 객체는 스레드 안전이어야한다는 것입니다.
휘발성 Bean 모드에서 Javabean의 모든 데이터 구성원은 휘발성 유형이며 Getter 및 Setter 메소드는 매우 평범해야합니다. 해당 속성을 얻거나 설정하는 것 외에는 논리를 포함 할 수 없습니다. 또한, 객체에서 참조 된 데이터 멤버의 경우 참조 된 객체는 유효하고 불변이어야합니다. (배열 참조가 휘발성으로 선언되면 배열 자체가 아닌 참조 만 휘발성 의미를 갖기 때문에 배열 값으로 속성을 금지합니다). 휘발성 변수의 경우 불변량 또는 제약 조건에는 Javabean 속성을 포함 할 수 없습니다. Listing 5의 예는 휘발성 Bean 패턴을 준수하는 Javabeans를 보여줍니다.
모드 #4 : "휘발성 Bean"모드
@threadsafe public class person {개인 휘발성 문자열 FirstName; 개인 휘발성 문자열 마지막 이름; 개인 휘발성 int 연령; 공개 문자열 getFirstName () {return firstName; } public String getLastName () {return lastName; } public int getage () {반환 연령; } public void setFirstName (String FirstName) {this.firstName = FirstName; } public void setLastName (String lastName) {this.lastName = lastName; } public void 설정 (int Age) {this.age = age; }}휘발성 고급 모드
이전 섹션에 설명 된 패턴은 대부분의 기본 사용 사례를 포괄하며 이러한 패턴에서 휘발성을 사용하는 것은 매우 유용하고 간단합니다. 이 섹션에서는 휘발성이 성능 또는 확장 성 장점을 제공하는보다 고급 모드를 소개합니다.
휘발성 응용 분야의 고급 모드는 매우 취약합니다. 따라서 가정은 신중하게 입증되어야하며,이 패턴은 매우 작은 변경조차도 코드를 손상시킬 수 있기 때문에 엄격하게 캡슐화됩니다! 마찬가지로,보다 고급 휘발성 사용 가능한 사용 사례를 사용하는 이유는 성능을 향상시켜 고급 패턴을 적용하기 전에이 성능 이점을 달성해야한다고 실제로 결정할 수 있기 때문입니다. 이러한 패턴에 대한 상충 관계가있어 성능 향상이 필요하지 않은 경우 (또는 엄격한 테스트 프로그램을 통해 필요하다는 것을 증명할 수 없다면), 돈을 잃고 포기하는 것보다 더 가치가 떨어질 가능성이 높기 때문에 나쁜 거래 일 가능성이 높습니다.
모드 #5 : 오버 헤드가 낮은 읽기 잠금 전략
지금까지 휘발성이 카운터를 구현할 수 없다는 것을 이해해야합니다. ++ X는 실제로 세 가지 작업 (읽기, 추가, 저장)의 간단한 조합이기 때문에 여러 스레드가 휘발성 카운터에서 동시에 증분 작업을 수행하려고하면 업데이트 된 값이 손실 될 수 있습니다.
그러나 읽기 작업이 쓰기 작업보다 훨씬 많으면 내부 잠금 및 휘발성 변수를 사용하여 공공 코드 경로의 오버 헤드를 줄일 수 있습니다. Listing 6에 표시된 스레드 안전 카운터는 동기화되어 증분 작업이 원자력이고 휘발성이되도록 현재 결과의 가시성을 보장합니다. 읽기 경로의 오버 헤드에는 휘발성 판독 작업 만 포함되기 때문에이 방법은 업데이트가 빈번하지 않으면 더 나은 성능을 달성 할 수 있습니다.
목록 6. 휘발성 및 동기화를 사용하여 "하위 오버 헤드 판 읽기 잠금 잠금"을 달성하십시오.
@threadsafe public class CheesyCounter {// 저렴한 읽기 write 잠금 요령을 사용합니다. // 모든 돌연변이 작업은 '이'잠금 홀드 @guardedby ( "this") 개인 휘발성 int 값으로 수행해야합니다. public int getValue () {return value; } public synchronized int excrement () {return value ++; }}이 기술을 "낮은 오버 헤드 읽기 쓰기 잠금"이라고하는 이유는 읽기 쓰기 작업에 다른 동기화 메커니즘을 사용하기 때문입니다. 이 예제의 쓰기 작업은 휘발성 사용의 첫 번째 조건을 위반하므로 휘발성으로 카운터를 안전하게 구현할 수 없습니다. 잠금을 사용해야합니다. 그러나 읽기 작업에서 휘발성을 사용하여 현재 값의 가시성을 보장 할 수 있으므로 잠금 장치를 사용하여 모든 변경 사항을 수행하고 휘발성과 함께 읽기 전용. 그중에서도 잠금은 하나의 스레드 만 한 번에 값에 액세스 할 수있게하며 휘발성은 여러 스레드가 읽기 작업을 수행 할 수 있도록합니다. 따라서 휘발성을 사용하여 코드 경로를 읽을 때는 읽기 쓰기 작업과 마찬가지로 모든 코드 경로를 실행하기 위해 잠금 장치를 사용하는 것보다 공유됩니다. 그러나이 모델의 약점을 명심하십시오.이 모델의 가장 기본적인 응용 프로그램이 그 이상이라면이 두 가지 경쟁 동기화 메커니즘을 결합하는 것이 매우 어려워집니다.
지시 재정의 및 발생하기 전의 규칙에 대해
1. 재주문하자
Java 언어 사양은 JVM 스레드가 내부적으로 순차적 의미를 유지한다고 규정합니다. 이 프로세스는 명령에 의해 재주문됩니다. 명령어 재정렬의 중요성은 JVM이 프로세서의 특성 (CPU의 다중 레벨 캐시 시스템, 멀티 코어 프로세서 등)에 따라 기계 명령을 적절하게 재주문 할 수 있으므로 기계 명령이 CPU의 실행 특성과 더 일치하고 기계의 성능을 최대화 할 수 있다는 것입니다.
프로그램 실행을위한 가장 간단한 모델은 지침을 실행하는 CPU와 무관 한 지침이 나타나는 순서대로 실행하는 것입니다. 이 모델의 전문 용어를 순차적 일관성 모델이라고합니다. 그러나 최신 컴퓨터 시스템 및 프로세서 아키텍처는이를 보장하지 않습니다 (인공 지정이 CPU 처리 특성을 준수하는 것을 항상 보장 할 수는 없기 때문입니다).
2. 전기 규칙에 따라
Java 스토리지 모델에는 이전의 원칙이 있습니다. 즉, ACTION B가 ACTION A의 실행 결과를보고 싶다면 (A/B가 동일한 스레드에서 실행되는지 여부에 관계없이) A/B는 관계를 만족시켜야합니다.
Java는 발생하는 규칙을 도입하기 전에 JMM 액션 (Java Memeory Model Action)과 같은 개념을 소개합니다. Java는 모델 액션을 저장합니다. 조치에는 다음이 포함됩니다 : 변수 읽기 및 쓰기, 잠금 모니터 및 릴리스 잠금, 스레드 start () 및 join (). 자물쇠는 나중에 언급됩니다.
완전한 규칙 전에 발생 :
(1) 동일한 스레드의 각 동작은 그 후에 나타나는 모든 조치에서 발생합니다.
(2) 모니터 잠금 해제는 동일한 모니터의 각 후속 잠금에 이르기 전에 발생합니다.
(3) 동일한 필드의 각 후속 읽기 작업에 이르기 전에 휘발성 필드에 대한 조작이 발생합니다.
(4) struch.start () 호출은 시작 스레드의 조치가 발생하기 전에 발생합니다.
(5) 스레드의 모든 동작은 다른 스레드에서 확인하기 전에이 스레드를 종료하거나 thread.join () 또는 thread.isalive () == false에서 리턴합니다.
(6) 하나의 스레드 A는 다른 스레드 B의 인터럽트 ()을 호출합니다. 스레드 A가 B가 A에 의해 B가 중단되는 것을 찾을 때 발생합니다 (B 예외를 던지거나 검출 된 B가 중단 된 () 또는 인터럽트 된 ()).
(7) 객체 생성자의 끝은 객체의 최종화기 시작이 발생합니다.
(8) 조치가 발생하기 전에 행동이 발생하고 B 조치가 발생하고 C 행동이 발생하면 행동이 진행되기 전에 행동이 발생합니다.
위는이 기사에 관한 것입니다. 여기에 소개하겠습니다. Java의 휘발성 변수를 배우고 이해하는 것이 도움이되기를 바랍니다.