JavaRush /Java Blog /Random-KO /흐름 관리. 휘발성 키워드와 Yield() 메서드

흐름 관리. 휘발성 키워드와 Yield() 메서드

Random-KO 그룹에 게시되었습니다
안녕하세요! 우리는 계속해서 멀티스레딩을 연구하고 있으며 오늘은 새로운 키워드인 휘발성과 Yield() 메서드에 대해 알게 될 것입니다. 그것이 무엇인지 알아봅시다 :)

키워드 휘발성

멀티스레드 애플리케이션을 만들 때 두 가지 심각한 문제에 직면할 수 있습니다. 첫째, 멀티 스레드 애플리케이션이 작동하는 동안 서로 다른 스레드가 변수 값을 캐시할 수 있습니다 . 이에 대해서는 "휘발성 사용" 강의에서 자세히 설명하겠습니다 . 한 스레드가 변수 값을 변경했지만 두 번째 스레드는 자체 캐시된 변수 복사본으로 작업 중이었기 때문에 이 변경 사항을 확인하지 못했을 가능성이 있습니다. 당연히 그 결과는 심각할 수 있습니다. 이것이 일종의 "변수"가 아니라 예를 들어 갑자기 무작위로 앞뒤로 점프하기 시작한 은행 카드 잔액이라고 상상해보십시오. :)별로 즐겁지 않습니까? 둘째, Java에서는 long및를 제외한 모든 유형의 필드에 대한 읽기 및 쓰기 작업이 double원자적입니다. 원자성이란 무엇입니까? 예를 들어, 한 스레드에서 변수 값을 변경 int하고 다른 스레드에서 이 변수의 값을 읽으면 이전 값이나 새 값(변경 후 밝혀진 값)을 얻게 됩니다. 스레드 1. 거기에는 "중간 옵션"이 나타나지 않을 수도 있습니다. 그러나 이는 long및 에서는 double작동하지 않습니다 . 왜? 크로스 플랫폼이기 때문이죠. Java 원칙은 "한 번 작성하면 어디에서나 작동한다"는 첫 번째 수준에서 어떻게 말했는지 기억하십니까? 이것은 크로스 플랫폼입니다. 즉, Java 애플리케이션은 완전히 다른 플랫폼에서 실행됩니다. 예를 들어, Windows 운영 체제, 다양한 버전의 Linux 또는 MacOS 및 모든 곳에서 이 애플리케이션은 안정적으로 작동합니다. long- doubleJava에서 가장 "무거운" 프리미티브: 무게는 64비트입니다. 그리고 일부 32비트 플랫폼은 64비트 변수를 읽고 쓰는 원자성을 구현하지 않습니다. 이러한 변수는 두 가지 작업으로 읽고 쓰여집니다. 먼저 처음 32비트가 변수에 기록되고 그 다음에는 또 다른 32비트가 기록됩니다. 따라서 이러한 경우 문제가 발생할 수 있습니다. 한 스레드는 64비트 값을 변수에 씁니다.Х, 그리고 그는 그것을 "두 단계로" 수행합니다. 동시에 두 번째 스레드는 이 변수의 값을 읽으려고 시도하며 처음 32비트가 이미 기록되었지만 두 번째 비트는 아직 기록되지 않은 중간에 바로 수행합니다. 결과적으로 중간의 잘못된 값을 읽어 오류가 발생합니다. 예를 들어, 이러한 플랫폼에서 변수에 숫자(9223372036854775809)를 쓰려고 하면 변수는 64비트를 차지하게 됩니다. 이진 형식에서는 다음과 같습니다. 100000000000000000000000000000000000000000000000000000000000000001 첫 번째 스레드는 이 숫자를 변수에 쓰기 시작하고 처음 32비트를 먼저 씁니다. 100000000000000000000000 0000 00000 그리고 두 번째 32: 00000000000000000000000000000001 그리고 두 번째 스레드가 이 간격에 끼어들어 변수의 중간 값인 100000000000000000000000000000000(이미 기록된 처음 32비트)을 읽습니다. 십진수 체계에서 이 숫자는 2147483648과 같습니다. 즉, 우리는 숫자 9223372036854775809를 변수에 쓰고 싶었지만 일부 플랫폼에서의 이 작업은 원자적이지 않기 때문에 "왼쪽" 숫자 2147483648을 얻었습니다. , 필요하지 않은 것이 갑자기 발생하며 이것이 프로그램 작동에 어떤 영향을 미칠지는 알 수 없습니다. 두 번째 스레드는 변수가 최종적으로 작성되기 전에 단순히 변수 값을 읽습니다. 즉, 첫 번째 32비트는 보았지만 두 번째 32비트는 보지 못했습니다. 물론 이러한 문제는 어제 발생한 것이 아니며 Java에서는 휘발성 키워드 하나만 사용하여 해결됩니다 . 프로그램에서 휘발성이라는 단어를 사용하여 일부 변수를 선언하면...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…이것은 다음을 의미합니다.
  1. 항상 원자적으로 읽고 쓰여집니다. 64비트 double이거나 long.
  2. Java 시스템은 이를 캐시하지 않습니다. 따라서 10개의 스레드가 로컬 복사본으로 작업하는 상황은 제외됩니다.
이것은 매우 심각한 두 가지 문제를 한 단어로 해결하는 방법입니다 :)

수확량() 메서드

우리는 이미 클래스의 여러 메서드를 살펴보았지만 Thread여러분에게 새로워질 중요한 메서드가 하나 있습니다. 이것이 Yield() 메소드 입니다 . 영어에서 "give in"으로 번역되었습니다. 이것이 바로 이 방법이 하는 일입니다! 흐름 관리.  휘발성 키워드와 Yield() 메소드 - 2스레드에서 Yield 메서드를 호출하면 실제로 다른 스레드에 다음과 같이 말합니다. 급하지 않아.” 작동 방식에 대한 간단한 예는 다음과 같습니다.
public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + "give way to others");
       Thread.yield();
       System.out.println(Thread.currentThread().getName() + " has finished executing.");
   }

   public static void main(String[] args) {
       new ThreadExample();
       new ThreadExample();
       new ThreadExample();
   }
}
Thread-0, Thread-1및 3개의 스레드를 순차적으로 생성하고 실행합니다 Thread-2. Thread-0먼저 시작하고 즉시 다른 사람에게 양보합니다. 그 후에는 시작되고 Thread-1또한 무너집니다. 그 후에는 시작되는데 Thread-2, 이 또한 열등합니다. 더 이상 스레드가 없으며 Thread-2마지막 스레드가 그 자리를 포기한 후 스레드 스케줄러는 다음과 같이 확인합니다. “그렇다면 더 이상 새 스레드가 없습니다. 대기열에 누가 있습니까? 이전에 자신의 자리를 마지막으로 포기한 사람은 누구입니까 Thread-2? 내 생각엔 그랬던 것 같은데 Thread-1? 알았어, 그럼 끝내자.” Thread-1끝까지 작업을 수행한 후 스레드 스케줄러는 계속해서 조정합니다. “좋아요, Thread-1이 완료되었습니다. 우리 말고도 줄 사람 있어?" 대기열에 Thread-0이 있습니다. Thread-1 직전에 자리를 포기했습니다. 이제 그 일이 그에게 이르렀고 그는 끝까지 행하여지는 중이니라 그 후 스케줄러는 스레드 조정을 완료합니다. “좋아요, Thread-2, 다른 스레드에 양보했습니다. 그들은 이미 모두 작동했습니다. 당신은 마지막으로 굴복했으니 이제 당신 차례입니다.” 그런 다음 Thread-2가 완료될 때까지 실행됩니다. 콘솔 출력은 다음과 같습니다. Thread-0은 다른 사람에게 양보합니다. Thread-1은 다른 사람에게 양보합니다. Thread-2는 다른 사람에게 양보합니다. Thread-1이 실행을 완료했습니다. Thread-0이 실행을 마쳤습니다. Thread-2가 실행을 마쳤습니다. 물론 스레드 스케줄러는 스레드를 다른 순서로 실행할 수 있지만(예: 0-1-2 대신 2-1-0) 원칙은 동일합니다.

발생 전 규칙

오늘 우리가 마지막으로 다룰 것은 " 이전 발생 " 원칙입니다. 이미 알고 있듯이 Java에서는 작업을 완료하기 위해 스레드에 시간과 리소스를 할당하는 대부분의 작업이 스레드 스케줄러에 의해 수행됩니다. 또한 스레드가 임의의 순서로 실행되는 방식을 두 번 이상 보았으며 대부분 예측이 불가능합니다. 그리고 일반적으로 이전에 수행했던 "순차적" 프로그래밍 이후에는 멀티스레딩이 무작위처럼 보입니다. 이미 본 것처럼 다중 스레드 프로그램의 진행은 전체 방법 세트를 사용하여 제어할 수 있습니다. 그러나 이 외에도 Java 멀티스레딩에는 또 다른 "안정성의 섬"이 있습니다. 즉 " 이전 발생 "이라는 4가지 규칙이 있습니다. 문자 그대로 영어에서 이것은 "이전에 발생했습니다"또는 "이전에 발생했습니다"로 번역됩니다. 이 규칙의 의미는 이해하기 매우 간단합니다. A두 개의 스레드 - 및 가 있다고 상상해보십시오 B. 이러한 각 스레드는 작업을 수행할 수 1있습니다 2. 그리고 각 규칙에서 " A가 B보다 먼저 발생합니다A "라고 말하면 이는 작업 전에 스레드에 의해 발생한 모든 변경 사항 과 이 작업에 수반된 변경 사항이 작업이 수행되는 시점에 스레드 1에 표시되고 수술이 수행된 후. 이러한 각 규칙은 다중 스레드 프로그램을 작성할 때 일부 이벤트가 다른 이벤트보다 100% 먼저 발생하고 작업 시 스레드가 작업 중에 스레드가 수행한 변경 사항을 항상 인식하도록 보장합니다. . 그들을 살펴보자. B2B2А1

규칙 1.

뮤텍스 해제는 다른 스레드가 동일한 모니터를 획득하기 전에 발생합니다 . 글쎄, 여기서는 모든 것이 분명해 보입니다. 객체나 클래스의 뮤텍스를 하나의 스레드(예: thread )가 획득하면 А다른 스레드(thread B)가 동시에 획득할 수 없습니다. 뮤텍스가 해제될 때까지 기다려야 합니다.

규칙 2.

method Thread.start() 이전에 발생합니다 Thread.run() . 복잡한 것도 없습니다. 이미 알고 있듯이 메서드 내부의 코드가 실행을 시작하려면 run()스레드에서 메서드를 호출해야 합니다 start(). 그것은 그의 것이지 방법 자체가 아닙니다 run()! 이 규칙은 Thread.start()실행 전에 설정된 모든 변수의 값이 실행을 시작한 메소드 내부에 표시되도록 보장합니다 run().

규칙 3.

메소드 종료 run() 전에 메소드 완료가 발생합니다 join(). А두 개의 스트림인 및 로 돌아가 보겠습니다 B. 스레드가 작업을 수행하기 전에 완료될 때까지 기다려야 하는 join()방식으로 메서드를 호출합니다 . 이는 객체 A의 메서드가 끝까지 확실히 실행된다는 것을 의미합니다. 그리고 스레드 메서드 에서 발생하는 모든 데이터 변경 사항은 완료를 기다리고 자체적으로 작업을 시작할 때 스레드에서 완전히 표시됩니다 . BArun()run()ABA

규칙 4.

휘발성 변수에 대한 쓰기는 동일한 변수에서 읽기 전에 발생합니다 . 휘발성 키워드를 사용하면 실제로 항상 현재 값을 얻을 수 있습니다. long및 의 경우에도 double앞서 논의한 문제가 있습니다. 이미 알고 있듯이 일부 스레드의 변경 사항이 다른 스레드에 항상 표시되는 것은 아닙니다. 그러나 물론 그러한 프로그램 동작이 우리에게 적합하지 않은 상황이 매우 자주 발생합니다. 스레드의 변수에 값을 할당했다고 가정해 보겠습니다 A.
int z;.

z= 555;
스레드가 B변수 값을 z콘솔에 인쇄하는 경우 할당된 값을 모르기 때문에 쉽게 0을 인쇄할 수 있습니다. 따라서 규칙 4는 변수를 z휘발성으로 선언하면 한 스레드의 해당 값 변경 사항이 항상 다른 스레드에서 표시된다는 것을 보장합니다. 이전 코드에 휘발성이라는 단어를 추가하면...
volatile int z;.

z= 555;
B...스트림이 콘솔에 0을 출력하는 상황은 제외됩니다. 휘발성 변수에 대한 쓰기는 변수에서 읽기 전에 발생합니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION