머리말
Java Concurrent 프로그래밍에 익숙한 사람은 JMM (Java Memory Model)에서 발생하는 (HB) 규칙이 Java 다중 스레드 작업의 질서와 가시성을 정의하여 프로그램 결과에 대한 컴파일러 재정렬의 영향을 방지한다는 것을 알고 있습니다.
Java 언어에는 "이전"의 규칙이 있습니다. Java 메모리 모델에서 정의 된 두 작업 사이의 부분 순서 관계입니다. 작업 A가 작동 B에서 먼저 발생하는 경우 작업 B가 발생하기 전에 작업 A의 영향은 작동 B에 의해 관찰 될 수 있음을 의미합니다. "영향"에는 메모리에서 공유 변수의 값을 수정, 메시지 보내기, 통화 방법 등을 기본적으로 시간의 시퀀스와 관련이 없습니다. 이 원칙은 특히 중요합니다. 데이터에 경쟁이 있는지 여부와 스레드가 안전한지 판단하는 주요 기초입니다.
공식 진술에 따르면 :
변수를 여러 스레드로 읽고 하나 이상의 스레드에 의해 작성되면 읽기와 쓰기 작업 사이에 HB 관계가 없으면 데이터 레이스 문제가 발생합니다.
B를 작동하는 스레드가 작동 A의 결과를 보도록하려면 (A와 B가 동일한 스레드에 관계없이) HB 원칙은 A와 B를 충족해야하며 그렇지 않은 경우 재정의로 이어질 수 있습니다.
HB 관계가 누락되면 재주문 문제가 발생할 수 있습니다.
HB의 규칙은 무엇입니까?
모두가 이것에 매우 익숙합니다. 대부분의 책과 기사가 소개됩니다. 여기에서 간단히 검토하겠습니다.
그중에서도 나는 배달 규칙을 굵게 만들었습니다. 전달 규칙을 능숙하게 사용하는 방법은 동기화를 달성하기위한 열쇠입니다.
그런 다음 다른 관점에서 HB를 설명하십시오. 작업이 B가 작동 할 때 B가 작동하면 공유 변수에서 작동 A의 작동 결과가 작동 B에 표시됩니다.
동시에, 작동 B HB가 C를 작동하는 경우, 공유 변수에서의 작동 A의 작동 결과는 작동 B로 보인다.
가시성을 달성하는 원리는 캐시 프로토콜 및 메모리 장벽입니다. 캐시 일관성 프로토콜 및 메모리 장벽을 통해 가시성이 달성됩니다.
동기화를 달성하는 방법?
Doug Lea의 저서 "Java Concurrency in Concurrency"에서 다음 설명은 다음과 같습니다.
이 책은 다음과 같이 언급합니다. HB의 일부 규칙을 결합하여 잠금 해제 된 보호 변수의 가시성을 달성 할 수 있습니다.
그러나이 기술은 진술 순서에 민감하기 때문에 오류가 발생하기 쉽습니다.
다음으로 저자는 휘발성 규칙 및 프로그램 주문 규칙을 통해 변수를 동기화하는 방법을 보여줍니다.
친숙한 예를 들어 봅시다 :
클래스 ThreadPrintDemo {static int num = 0; 정적 휘발성 부울 플래그 = false; public static void main (string [] args) {스레드 t1 = 새 스레드 (() -> {for (; 100> num;) {if (! flag && (num == 0 || ++ num % 2 == 0)) {system.out.println (num); flag = true;}}); 스레드 t2 = 새 스레드 (() -> {for (; 100> num;) {if (flag && (++ num % 2! = 0)) {system.out.println (num); flag = false;}}); t1.start (); t2.start (); }}이 코드의 목적은 두 스레드 사이에서 0-100을 인쇄하는 것입니다.
동시 프로그래밍에 익숙한 학생들은이 NUM 변수가 휘발성을 사용하지 않으며 가시성 문제, 즉 T1 스레드가 NUM을 업데이트했으며 T2 스레드가 인식 할 수 없다고 말해야합니다.
저자 인 하하는 처음에 그렇게 생각했지만 최근에 HB 규칙을 연구함으로써 NUM의 휘발성 수정을 제거해도 괜찮다는 것을 알았습니다.
분석하고 포스터가 사진을 그렸습니다.
이 그림을 분석합시다.
참고 : HB 규칙은 이전 작업의 결과가 다음 작업에 표시되도록합니다.
따라서, 위의 애플릿에서, 스레드 B는 NUM이 휘발성으로 수정되지 않더라도 스레드 A에 의한 NUM의 수정을 완전히 알고있다.
이러한 방식으로, 우리는 HB 원칙을 사용하여 변수의 동기 작동, 즉 다중 스레드 환경에서 공유 변수의 동시 수정의 보안을 보장합니다. 이 변수에 대한 Java 프리미티브는 휘발성 및 동기화 및 CAS (Counts)입니다.
이것은 안전하지 않은 것처럼 보일 수 있으며 이해하기 쉽지 않을 수 있습니다. 이 모든 것이 기본 HB의 캐시 프로토콜과 메모리 배리어에 의해 구현되기 때문입니다.
동기화를 달성하기위한 다른 규칙
스레드 종료 규칙을 사용한 구현 :
정적 int a = 1; public static void main (String [] args) {Thread TB = 새 스레드 (() -> {a = 2;}); 스레드 ta = 새 스레드 (() -> {try {tb.join ();} catch (InterpruptedException e) {// no} system.out.println (a);}); ta.start (); tb.start (); } 스레드 시작 규칙을 사용하여 구현합니다.
정적 int a = 1; public static void main (String [] args) {Thread TB = new Thread (() -> {System.out.println (a);}); 스레드 ta = 새 스레드 (() -> {tb.start (); a = 2;}); ta.start (); }이 두 작업은 또한 변수의 가시성을 보장 할 수 있습니다.
그것은 이전 개념을 실제로 전복합니다. 이전 개념에서 변수가 휘발성 또는 최종에 의해 변수를 수정하지 않으면 멀티 스레딩에서 읽기 및 쓰기는 확실히 안전하지 않습니다. 캐시가 있기 때문에 최신이 아닌 독서가 발생하지 않기 때문입니다.
그러나 HB를 사용하면이를 달성 할 수 있습니다.
요약
이 기사의 제목은 발생하기 전에 공유 변수의 동기화 된 작업을 실현하는 것이지만, 주요 목적은 더 깊이 발생하는 것을 이해하는 것입니다. 이전의 개념이 실제로 다음 작업에 대한 이전 조작의 질서를 보장하는 것과 작동의 가시성을 보장하는 것이 다중 스레드 환경을 초래하는 것입니다.
동시에, 전이 규칙을 유연하게 사용하고 규칙을 결합함으로써 두 스레드를 동기화 할 수 있습니다. 프리미티브를 사용하지 않고 지정된 공유 변수의 구현도 가시성을 보장 할 수 있습니다. 이것은 읽기가 매우 쉽지는 않지만 시도이기도합니다.
Doug Lea는 JUC에서 규칙을 결합하여 동기화를 달성하는 방법에 대한 실습을 제공합니다.
예를 들어, 이전 버전의 FutureTask (사라짐)의 내부 클래스 동기화는 TryLeasShared 방법을 통해 휘발성 변수를 수정하고 휘발성 변수를 읽습니다.
이는 TryLeasShared 전에 비 휘발성 결과 변수를 설정 한 다음 TryAcquireshared 후 결과 변수를 읽음으로써 프로그램 주문 규칙을 활용합니다.
이것은 결과 변수의 가시성을 보장합니다. 첫 번째 예와 유사 : 프로그램 주문 규칙 및 휘발성 규칙을 사용하여 정상적인 변수 가시성을 달성합니다.
Doug Lea는이 "도움의 사용"기술이 오류가 발생하기 쉬우 며주의해서 사용해야한다고 말했다. 그러나 어떤 경우에는 이런 종류의 "레버리지"가 매우 합리적입니다.
실제로, Blockingqueue는 또한 "사용하기 전에"사용되었습니다. 잠금 해제 규칙을 기억하십니까? 잠금 해제가 발생하면 내부 요소가 표시되어야합니다.
클래스 라이브러리에는 동시 컨테이너, CountdownLatch, Semaphore, Future, Executor, Cyclicbarrier, Exchanger 등이 발생하는 원칙을 "사용"하는 다른 작업이 있습니다.
간단히 말해서 :
이전의 원칙은 JMM의 핵심입니다. HB 원칙이 충족 될 때만 주문할 수 있고 가시성이 보장되며, 그렇지 않으면 컴파일러가 코드를 재정렬합니다. HB는 잠금 및 휘발성에 대한 규칙을 정의합니다.
HB 규칙의 적절한 조합으로 일반 공유 변수의 올바른 사용을 달성 할 수 있습니다.
좋아, 위는이 기사의 전체 내용입니다. 이 기사의 내용에 모든 사람의 연구 나 작업에 대한 특정 참조 가치가 있기를 바랍니다. 궁금한 점이 있으면 의사 소통을 위해 메시지를 남길 수 있습니다. Wulin.com을 지원 해주셔서 감사합니다.