CodeGym /Java Blog /무작위의 /스레드 관리. 휘발성 키워드 및 yield() 메서드
John Squirrels
레벨 41
San Francisco

스레드 관리. 휘발성 키워드 및 yield() 메서드

무작위의 그룹에 게시되었습니다
안녕! 우리는 멀티스레딩에 대한 연구를 계속합니다. volatile오늘은 키워드와 방법에 대해 알아보도록 하겠습니다 yield(). 다이빙하자 :)

휘발성 키워드

다중 스레드 응용 프로그램을 만들 때 두 가지 심각한 문제에 직면할 수 있습니다. 첫째, 다중 스레드 응용 프로그램이 실행 중일 때 여러 스레드가 변수 값을 캐시할 수 있습니다 ( '휘발성 사용'이라는 강의 에서 이미 이에 대해 이야기했습니다 ). 하나의 스레드가 변수의 값을 변경하지만 두 번째 스레드는 변수의 캐시된 복사본으로 작업하기 때문에 변경 사항을 볼 수 없는 상황이 있을 수 있습니다 . 당연히 그 결과는 심각할 수 있습니다. 오래된 변수가 아니라 은행 계좌 잔액이 갑자기 무작위로 위아래로 뛰기 시작한다고 가정해 보겠습니다. 재미있을 것 같지 않죠? 둘째, Java에서 모든 기본 유형을 읽고 쓰는 작업,longdouble, 원자입니다. 예를 들어, 한 스레드에서 변수 값을 변경 int하고 다른 스레드에서 변수 값을 읽으면 이전 값 또는 새 값, 즉 변경 결과 값을 얻게 됩니다. 스레드 1에서. '중간 값'이 없습니다. long그러나 이것은 s와 doubles 에서는 작동하지 않습니다 . 왜? 크로스 플랫폼 지원 때문입니다. 시작 단계에서 Java의 기본 원칙이 '한 번 작성하고 어디서나 실행'이라고 말했던 것을 기억하십니까? 이는 크로스 플랫폼 지원을 의미합니다. 즉, Java 애플리케이션은 모든 종류의 다양한 플랫폼에서 실행됩니다. 예를 들어 Windows 운영 체제에서 다른 버전의 Linux 또는 MacOS가 있습니다. 그것은 그들 모두에 장애없이 실행됩니다. 64비트로 계산하면,longdoubleJava에서 '가장 무거운' 프리미티브입니다. 그리고 특정 32비트 플랫폼은 64비트 변수의 원자적 읽기 및 쓰기를 구현하지 않습니다. 이러한 변수는 두 가지 작업으로 읽고 씁니다. 먼저 처음 32비트가 변수에 기록된 다음 다른 32비트가 기록됩니다. 결과적으로 문제가 발생할 수 있습니다. 한 스레드는 변수에 일부 64비트 값을 기록 X하고 두 작업으로 수행합니다. 동시에, 두 번째 스레드는 변수의 값을 읽으려고 시도하고 두 작업 사이에 읽기를 시도합니다. 즉, 첫 번째 32비트는 작성되었지만 두 번째 32비트는 작성되지 않은 경우입니다. 결과적으로 잘못된 중간 값을 읽고 버그가 있습니다. 예를 들어, 그러한 플랫폼에서 숫자를 9223372036854775809 에 쓰려고 하면 변수에 64비트를 차지합니다. 이진 형식으로 보면 다음과 같습니다. 1000000000000000000000000000000000000000000000000000000000000001 첫 번째 스레드가 변수에 숫자를 쓰기 시작합니다. 처음에는 처음 32비트 (1000000000000000000000000000000) 를 쓰고 두 번째 32비트 (0000000000000000000000000000001) 를 씁니다. 그리고 두 번째 스레드는 이미 작성된 처음 32비트인 변수의 중간 값(10000000000000000000000000000000)을 읽으면서 이러한 작업 사이에 끼어들 수 있습니다. 십진법에서 이 숫자는 2,147,483,648입니다. 즉, 숫자 9223372036854775809를 변수에 쓰고 싶었지만 이 작업이 일부 플랫폼에서 원자적이지 않기 때문에 갑자기 나온 사악한 숫자 2,147,483,648이 있고 알 수 없는 효과가 있습니다. 프로그램. 두 번째 스레드는 쓰기가 완료되기 전에 단순히 변수 값을 읽습니다. 즉, 스레드는 첫 번째 32비트를 보았지만 두 번째 32비트는 보지 않았습니다. 물론 이러한 문제는 어제 발생한 것이 아닙니다. Java는 단일 키워드로 문제를 해결합니다 volatile. 우리가 사용하는 경우volatile프로그램에서 일부 변수를 선언할 때 키워드…

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

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

yield() 메서드

우리는 이미 클래스의 많은 Thread메서드를 검토했지만 처음 알게 될 중요한 메서드가 있습니다. yield()방법 입니다 . 그리고 그것은 그 이름이 의미하는 바를 정확히 수행합니다! 스레드 관리.  휘발성 키워드 및 yield() 메서드 - 2스레드에서 메서드를 호출하면 yield실제로 다른 스레드에 '안녕하세요. 나는 특별히 서두르지 않고 있으므로 프로세서 시간을 확보하는 것이 중요한 사람이라면 가져가십시오. 기다릴 수 있습니다.' 이것이 어떻게 작동하는지에 대한 간단한 예는 다음과 같습니다.

public class ThreadExample extends Thread {

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

   public void run() {

       System.out.println(Thread.currentThread().getName() + " yields its place 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, 의 세 가지 스레드를 순차적으로 생성하고 시작합니다 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-0이 실행을 마쳤습니다. 스레드-2가 실행을 마쳤습니다. 물론 스레드 스케줄러는 다른 순서(예: 0-1-2 대신 2-1-0)로 스레드를 시작할 수 있지만 원칙은 동일합니다.

Happens-before 규칙

오늘 마지막으로 다룰 내용은 ' 일어나기 전에 '라는 개념입니다. 이미 알고 있듯이 Java에서 스레드 스케줄러는 작업을 수행하기 위해 스레드에 시간과 리소스를 할당하는 작업과 관련된 대부분의 작업을 수행합니다. 또한 일반적으로 예측할 수 없는 임의의 순서로 스레드가 실행되는 방식을 반복해서 보았습니다. 그리고 일반적으로 이전에 수행한 '순차적' 프로그래밍 이후에는 멀티스레드 프로그래밍이 무작위로 보입니다. 다중 스레드 프로그램의 흐름을 제어하기 위해 여러 가지 방법을 사용할 수 있다고 이미 믿게 되었습니다. 그러나 Java의 멀티스레딩에는 한 가지 기둥이 더 있습니다. 바로 4개의 ' 일어나기 전에 ' 규칙입니다. 이러한 규칙을 이해하는 것은 매우 간단합니다. 두 개의 스레드가 있다고 상상해보십시오 A.B. 이러한 각 스레드는 작업을 수행할 수 1있으며 2. 각 규칙에서 ' A 발생-B 이전 ' 이라고 하는 것은 A작업 전에 스레드에 의해 변경된 모든 내용 과 이 작업으로 인한 변경 내용이 작업이 수행될 때와 그 이후에 1스레드에 표시됨을 의미합니다. 각 규칙은 멀티스레드 프로그램을 작성할 때 특정 이벤트가 다른 이벤트보다 100% 먼저 발생하고 작업 시 스레드가 작업 중에 스레드가 변경한 사항을 항상 인식하도록 보장합니다 . 그것들을 검토해 봅시다. B22BA1

규칙 1.

뮤텍스 해제는 다른 스레드가 동일한 모니터를 획득하기 전에 발생합니다 . 나는 당신이 여기에서 모든 것을 이해한다고 생각합니다. 객체 또는 클래스의 뮤텍스가 하나의 스레드, 예를 들어 thread 에 의해 획득되면 A다른 스레드(thread B)는 동시에 이를 획득할 수 없습니다. 뮤텍스가 해제될 때까지 기다려야 합니다.

규칙 2.

Thread.start()방법은 전에 발생합니다 Thread.run() . 다시 말하지만 여기서 어려운 것은 없습니다. 메서드 내에서 코드 실행을 시작하려면 스레드에서 메서드를 run()호출해야 한다는 것을 이미 알고 있습니다. start()특히 메서드 자체가 아니라 시작 메서드입니다 run()! 이 규칙은 가 호출되기 전에 설정된 모든 변수의 값이 시작되면 메서드 Thread.start()내에서 표시되도록 합니다 run().

규칙 3.

run()메서드 의 끝은 메서드 에서 반환되기 전에 발생합니다join() . 두 개의 스레드로 돌아가 보겠습니다. AB. join()스레드가 작업을 수행하기 전에 B스레드 완료를 기다리도록 메서드를 호출합니다 . A이는 A 객체의 run()메서드가 끝까지 실행된다는 것을 의미합니다. run()그리고 스레드 메서드 에서 발생하는 데이터에 대한 모든 변경 사항은 스레드 가 자체 작업을 시작할 수 있도록 스레드가 작업을 완료하기를 기다리는 작업이 완료되면 스레드 A에서 볼 수 있도록 100% 보장됩니다 .BA

규칙 4.

volatile변수 에 쓰기는 동일한 변수에서 읽기 전에 발생합니다 . 키워드 를 사용하면 volatile실제로 항상 현재 값을 얻습니다. 또는 long( double여기서 발생할 수 있는 문제에 대해 앞에서 이야기했습니다). 이미 알고 있듯이 일부 스레드에서 변경한 내용이 다른 스레드에 항상 표시되는 것은 아닙니다. 그러나 물론 그러한 행동이 우리에게 적합하지 않은 상황이 매우 자주 발생합니다. 스레드의 변수에 값을 할당한다고 가정합니다 A.

int z;

….

z = 555;
B스레드가 콘솔에 변수 값을 표시해야 하는 경우 z할당된 값을 모르기 때문에 쉽게 0을 표시할 수 있습니다. z그러나 규칙 4는 변수를 로 선언하면 volatile한 스레드에서 해당 값을 변경하면 항상 다른 스레드에서 볼 수 있음을 보장합니다. volatile이전 코드에 단어를 추가하면 ...

volatile int z;

….

z = 555;
B...그런 다음 스레드가 0을 표시할 수 있는 상황을 방지합니다. volatile변수를 읽기 전에 변수에 쓰기가 발생합니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION