경합 조건은 임계 섹션 내부에서 발생할 수 있는 특수 조건입니다. 임계 섹션은 여러 스레드에 의해 실행되는 코드 섹션이며 스레드 실행 순서는 임계 섹션의 동시 실행 결과에 영향을 미칩니다.
여러 스레드가 임계 섹션을 실행할 때 스레드 실행 순서에 따라 결과가 다를 수 있습니다. 이 임계 섹션에는 경쟁 조건이 포함되어 있습니다. 이 경쟁 조건에 대한 용어는 스레드가 임계 섹션을 통해 경주하고 있으며 이 경주의 결과가 임계 섹션 실행 결과에 영향을 미친다는 비유에서 유래되었습니다.
좀 복잡하게 들릴 수도 있으므로 다음 섹션에서 경쟁 조건과 중요한 부분에 대해 자세히 설명하겠습니다.
중요 섹션
동일한 애플리케이션 내에서 두 개 이상의 스레드를 실행해도 자체적으로 문제가 발생하지 않습니다. . 여러 스레드가 동일한 리소스에 액세스하면 문제가 발생합니다. 예를 들어 동일한 메모리(변수, 배열 또는 개체), 시스템(데이터베이스, 웹 서비스) 또는 파일입니다.
사실 하나 이상의 스레드가 이러한 리소스에 쓰면 문제가 발생할 수 있습니다. 리소스가 변경되지 않는 한 여러 스레드가 동일한 리소스를 읽는 것이 안전합니다.
다음은 여러 스레드가 동시에 실행되는 경우 실패할 수 있는 예입니다.
public class Counter { protected long count = 0; public void add(long value){ this.count = this.count + value; } }
스레드 A와 B가 동일한 작업을 실행한다고 상상해 보세요. Counter 클래스 인스턴스의 메소드를 추가합니다. 운영 체제가 스레드 간에 언제 전환하는지 알 수 있는 방법이 없습니다. add 메소드의 코드는 JVM(Java Virtual Machine)에서 별도의 원자적 명령으로 실행되지 않습니다. 대신 다음과 유사한 일련의 더 작은 명령어 세트로 실행됩니다.
메모리에서 레지스터로 this.count 값을 읽어옵니다.
레지스터에 값을 추가합니다.
레지스터의 값을 다시 메모리에 씁니다.
스레드 A와 B가 함께 실행될 때 어떤 일이 발생하는지 관찰하세요.
this.count = 0; A: Reads this.count into a register (0) B: Reads this.count into a register (0) B: Adds value 2 to register B: Writes register value (2) back to memory. this.count now equals 2 A: Adds value 3 to register A: Writes register value (3) back to memory. this.count now equals 3
이 두 스레드는 카운터에 2와 3을 추가하려고 합니다. 가운데. 따라서 이 두 스레드의 실행이 완료된 후의 값은 5가 되어야 합니다. 그러나 두 스레드가 실행 중에 인터리브되기 때문에 결과가 달라집니다.
위에서 언급한 실행 시퀀스 예에서 두 스레드는 모두 메모리에서 값 0을 읽습니다. 그런 다음 해당 값에 각각의 값인 2와 3을 더하고 결과를 다시 메모리에 씁니다. 5 대신 this.count에 남은 값은 마지막 스레드가 쓴 값이 됩니다. 위의 예에서는 스레드 A이지만 스레드 B일 수도 있습니다.
중요 섹션의 경쟁 조건
위 예에서 add 메소드의 코드에는 중요 섹션이 포함되어 있습니다. 여러 스레드가 이 임계 섹션을 실행하면 경쟁 조건이 발생합니다.
보다 공식적으로 두 스레드가 동일한 리소스를 놓고 경쟁하고 리소스에 액세스하는 순서가 중요한 상황을 경쟁 조건이라고 합니다. 경쟁 조건을 발생시키는 코드 섹션을 임계 섹션이라고 합니다.
경합 조건 방지
경합 조건이 발생하지 않도록 하려면 실행 중인 임계 섹션이 원자적 명령으로 실행되도록 해야 합니다. 이는 단일 스레드가 이를 실행하면 첫 번째 스레드가 임계 섹션을 떠날 때까지 다른 스레드가 이를 실행할 수 없음을 의미합니다.
임계 섹션에서 스레드 동기화를 사용하면 경쟁 조건을 피할 수 있습니다. 스레드 동기화는 Java 코드의 동기화 잠금을 사용하여 얻을 수 있습니다. 스레드 동기화는 잠금이나 java.util.concurrent.atomic.AtomicInteger와 같은 원자 변수와 같은 다른 동기화 개념을 사용하여 달성할 수도 있습니다.
중요 섹션 처리량
전체 중요 섹션에 대한 동기화 잠금이 작동할 수 있는 소규모 중요 섹션의 경우. 그러나 더 큰 중요 섹션의 경우 여러 스레드가 각각의 더 작은 중요 섹션을 실행할 수 있도록 더 작은 중요 섹션으로 나누는 것이 더 합리적입니다. 공유 자원에 대한 경합을 줄이고 전체 임계 영역의 처리량을 늘릴 수 있습니다.
다음은 매우 간단한 Java 예입니다.
public class TwoSums { private int sum1 = 0; private int sum2 = 0; public void add(int val1, int val2){ synchronized(this){ this.sum1 += val1; this.sum2 += val2; } } }
add 메소드가 두 합계 변수에 값을 추가하는 방법에 유의하세요. 경쟁 조건을 방지하기 위해 내부적으로 수행되는 합계에는 Java 동기화 잠금이 있습니다. 이 구현을 사용하면 한 번에 하나의 스레드만 이 합계를 수행할 수 있습니다.
그러나 두 합계 변수는 서로 독립적이므로 다음과 같이 두 개의 별도 동기화 잠금으로 분리할 수 있습니다.
public class TwoSums { private int sum1 = 0; private int sum2 = 0; public void add(int val1, int val2){ synchronized(this){ this.sum1 += val1; } synchronized(this){ this.sum2 += val2; } } }
두 개의 스레드가 이 add 메소드를 동시에 실행할 수 있다는 점에 유의하세요. 한 스레드는 첫 번째 동기화 잠금을 획득하고 다른 스레드는 두 번째 동기화 잠금을 획득합니다. 이렇게 하면 스레드가 서로 대기하는 시간이 줄어듭니다.
물론 이 예는 매우 간단합니다. 실제 생활에서는 공유 리소스의 중요 섹션 분리가 더 복잡할 수 있으며 실행 순서 가능성에 대한 더 많은 분석이 필요할 수 있습니다.
위 내용은 Java Race Condition 및 Critical 내용입니다. 더 많은 관련 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!