먼저 동기화 된에 대한 자세한 설명을 읽어 봅시다.
Synchronized는 Java 언어의 키워드입니다. 메소드 또는 코드 블록을 수정하는 데 사용되면 최대 한 스레드가 동시에 코드를 실행하도록 할 수 있습니다.
1. 두 개의 동시 스레드가 동일한 개체 객체에서 동기화 된 (이) 동기화 된 코드 블록에 액세스하면 한 번 이내에 하나의 스레드 만 실행할 수 있습니다. 다른 스레드는 코드 블록을 실행하기 전에 현재 스레드 가이 코드 블록을 실행할 때까지 기다려야합니다.
2. 그러나 한 스레드가 객체의 동기화 된 (이) 동기화 코드 블록에 액세스하면 다른 스레드가 해당 객체의 비 동기화 된 (이) 동기화 코드 블록에 액세스 할 수 있습니다.
3. 스레드가 객체의 동기화 된 (이) 동기화 코드 블록에 액세스 할 때 객체의 다른 모든 동기화 된 (이) 동기화 코드 블록에 액세스 할 수없는 다른 스레드가 차단되는 것이 특히 중요합니다.
4. 세 번째 예제는 다른 동기 코드 블록에도 적용됩니다. 즉, 스레드가 객체의 동기화 된 (이) 동기화 코드 블록에 액세스하면이 객체의 객체 잠금을 얻습니다. 결과적으로, 다른 스레드는 객체 객체의 모든 동기 코드 부분에 대한 액세스가 일시적으로 차단됩니다.
5. 위 규칙은 다른 객체 잠금에도 적용됩니다.
간단히 말해서, 동기화 된 것은 현재 스레드의 잠금을 선언합니다. 이 잠금 장치가있는 스레드는 블록에서 지침을 실행할 수 있으며 다른 스레드는 동일한 작업 전에 잠금이 획득 될 때만 기다릴 수 있습니다.
이것은 매우 유용하지만 또 다른 이상한 상황에 직면했습니다.
1. 같은 클래스에는 두 가지 방법이 있습니다 : 동기화 된 키워드 선언 사용
2. 메소드 중 하나를 실행할 때는 다른 메소드 (비동기 스레드 콜백)가 실행될 때까지 기다려야하므로 CountdownLatch를 사용하여 대기합니다.
3. 코드는 다음과 같이 해체됩니다.
동기화 된 void a () {countdownlatch = new CountdownLatch (1); // 일부 countdownlatch.await ();} synchronized void b () {countdownlatch.countdown ();} ~에
방법 A는 기본 스레드에 의해 실행되며, 메소드 B는 비동기 스레드에 의해 실행되며 콜백 실행 결과는 다음과 같습니다.
메인 스레드는 A 메소드를 실행 한 후에 갇히기 시작하고 더 이상 수행하지 않으며, 얼마나 오래 걸리더라도 기다리는 것은 쓸모가 없습니다.
이것은 고전적인 교착 상태 문제입니다
B가 실행되기를 기다리는 A는 B가 콜백이라고 생각하지 않으며 B는 A가 실행되기를 기다리고 있습니다. 왜? 동기화 된 역할을 수행합니다.
일반적으로 코드 블록을 동기화하려면 공유 변수를 사용하여 잠금해야합니다.
바이트 [] mutex = new Byte [0]; void a1 () {synchronized (mutex) {// dosomething}} void b1 () {synchronized (mutex) {// dosomething}} A 메소드 및 B 방법의 내용이 각각 A1 및 B1 방법의 동기화 된 블록으로 마이그레이션되면 이해하기 쉽습니다.
A1이 실행되면 (CountdownLatch) B1 메소드가 실행되기를 간접적으로 기다립니다.
그러나 A1의 Mutex가 공개되지 않으므로 B1을 기다리기 시작합니다. 이 시점에서 비동기 콜백 B1 메소드가 MUTEX가 잠금을 해제 할 때까지 기다려야하더라도 B 방법은 실행되지 않습니다.
이로 인해 교착 상태가 발생했습니다!
여기서 동기화 된 키워드는 메소드 앞에 배치되며 함수는 동일합니다. Java 언어는 Mutx의 선언과 사용을 숨기는 데 도움이됩니다. 동일한 객체에 사용 된 동기화 된 방법은 동일하므로 비동기 콜백조차도 교착 상태가 발생 하므로이 문제에주의하십시오. 이 수준의 오류는 동기화 된 키워드가 부적절하게 사용된다는 것입니다. 무작위로 사용하지 말고 올바르게 사용하십시오.
그렇다면 보이지 않는 뮤 테스 물체는 정확히 무엇입니까?
예제 자체는 생각하기 쉽습니다. 이런 식으로, 새 객체를 정의하고 잠금을 만들 필요가 없기 때문입니다. 이 아이디어를 증명하기 위해 프로그램을 작성하여이를 증명할 수 있습니다.
아이디어는 매우 간단합니다. 클래스를 정의하면 두 가지 방법이 있습니다. 하나는 동기화되고 다른 하나는 메소드 본문에서 동기화 된 (이)로 사용됩니다. 그런 다음 두 개의 스레드를 시작 하여이 두 가지 메소드를 별도로 호출하십시오. 두 가지 방법 (대기) 사이에서 잠금 경쟁이 발생하면이 메소드에 의해 선언 된 동기화 된 뮤 테스가 실제로 인스턴스 자체라는 것을 설명 할 수 있습니다.
public class multithreadsync {public synchronized void m1 ()는 InterpruptedException {system. out.println ( "M1 Call"); 실. 수면 (2000); 체계. out.println ( "M1 Call Done"); } public void m2 ()가 중단 된 exception {synchronized (this) {system. out.println ( "M2 Call"); 실. 수면 (2000); 체계. out.println ( "m2 호출 완료"); }} public static void main (String [] args) {Final MultithreadSync thisobj = new MultithreadSync (); 스레드 t1 = new Thread () {@override public void run () {try {thisobj.m1 (); } catch (InterruptedException e) {e.printstacktrace (); }}}}; 스레드 t2 = new Thread () {@override public void run () {try {thisobj.m2 (); } catch (InterruptedException e) {e.printstacktrace (); }}}; t1.start (); t2.start (); }} 결과 출력은 다음과 같습니다.
M1 Callm1 Call Donem2 Callm2 호출 완료
메소드 M2의 동기 블록이 M1의 실행을 기다리고 있다고 설명된다. 위의 개념을 확인할 수 있습니다.
동기화가 정적 메소드에 추가 될 때 클래스 수준 메소드이므로 잠긴 객체는 현재 클래스의 클래스 인스턴스입니다. 당신은 또한 그것을 증명하기 위해 프로그램을 작성할 수 있습니다. 여기에서 생략됩니다.
따라서 메소드의 동기화 된 키워드는 읽을 때 동기화 된 (this) {}로 자동으로 교체 할 수 있습니다. 이해하기 쉽습니다.
void method () {void synchronized method () {synchronized (this) {// biz code // biz code} ------ >>>}} 동기화 된 메모리 가시성
Java에서는 키워드 동기가 스레드 간의 상호 배제를 구현하는 데 사용될 수 있지만, 다른 기능, 즉 메모리의 변수의 가시성을 보장하기 위해, 즉 두 스레드가 동일한 변수에 대한 동일한 변수에 대한 액세스를 동시에 읽고 쓰는 경우, 쓰레기가 변수를 다시 읽을 수 있도록하기 위해 Synchronized가 변수를 다시 읽을 수 있다는 것을 잊어 버린다는 것을 알고 있습니다.
예를 들어, 다음 예는 다음과 같습니다.
공개 클래스 novisibility {private static boolean ready = false; 개인 정적 int 번호 = 0; private static class readerthread는 스레드 {@override public void run () {while (! ready) {thread.yield (); // 다른 스레드가 작동하도록 CPU를 지원합니다} system.out.println (번호); }} public static void main (String [] args) {new readerThread (). start (); 숫자 = 42; ready = true; }}읽기 스레드가 출력 될 것이라고 생각하십니까? 42? 정상적인 상황에서는 42가 출력됩니다. 그러나 재정렬 문제로 인해 읽기 스레드는 0을 출력하거나 출력이 없습니다.
Java 코드를 Bytecode로 컴파일 할 때 컴파일러가 코드를 재정렬 할 수 있으며 CPU는 기계 지침을 실행할 때 지침을 재정렬 할 수도 있습니다. 재주문이 프로그램의 의미를 파괴하지 않는 한
단일 스레드에서 재주문이 프로그램의 실행 결과에 영향을 미치지 않는 한, 재주문이 다른 스레드에 큰 영향을 미칠 수 있더라도 프로그램에서 지정된 순서로 실행해야한다는 것을 보장 할 수 없습니다.
이는 "Ready = true"명령문의 실행이 "번호 = 42"문의 실행보다 우선 할 수 있음을 의미합니다. 이 경우 읽기 스레드는 숫자 0의 기본값을 출력 할 수 있습니다.
Java 메모리 모델에서 문제를 재정렬하면 이러한 메모리 가시성 문제가 발생합니다. Java 메모리 모델에서 각 스레드에는 자체 작업 메모리 (주로 CPU 캐시 또는 레지스터)가 있으며 변수에 대한 작업은 자체 작업 메모리에서 수행되는 반면 스레드 간의 통신은 기본 메모리와 스레드 작업 메모리 간의 동기화를 통해 달성됩니다.
예를 들어, 위의 예제에서, 쓰기 스레드는 숫자를 42로 성공적으로 업데이트하고 true로 준비했지만, 쓰기 스레드는 숫자 만 기본 메모리와 동기화 할 가능성이 매우 높습니다 (아마도 CPU의 쓰기 버퍼로 인해) 이후 읽기 스레드가 항상 거짓이기 때문에 위의 코드는 숫자 값을 출력하지 않습니다.
동기화 된 키워드를 사용하여 동기화하면 그러한 문제가 없습니다.
공개 클래스 novisibility {private static boolean ready = false; 개인 정적 int 번호 = 0; 개인 정적 객체 잠금 = 새 개체 (); private static class readerthread는 스레드 {@override public void run () {synchronized (lock) {while (! ready) {thread.yield (); } system.out.println (번호); }} public static void main (string [] args) {synchronized (lock) {new readerthread (). start (); 숫자 = 42; ready = true; }}} Java 메모리 모델은 동기 시맨틱에 대한 다음과 같은 보장을 제공하기 때문입니다.
즉, Threada가 잠금 M을 릴리스 할 때, 작성된 변수 (예 : 작업 메모리에 존재하는 X 및 Y 등)는 기본 메모리와 동기화됩니다. ThreadB가 동일한 잠금 M에 적용되면 ThreadB의 작업 메모리가 유효하지 않은 상태로 설정된 다음 ThreadB가 기본 메모리에서 액세스하려는 변수를 작업 메모리로 다시로드합니다 (현재 x = 1, y = 1은 THREADA에서 수정 된 최신 값입니다). 이런 식으로, Threada에서 Threadb까지의 스레드 사이의 통신이 달성됩니다.
이것은 실제로 JSR133에 의해 정의 된 규칙 중 하나입니다. JSR133은 Java 메모리 모델에 대한 다음과 같은 다음의 규칙을 정의합니다.
실제로,이 사건이 발생하기 쉬운 규칙 세트는 작업 간의 메모리 가시성을 정의합니다. A가 B 작동하기 전에 작동하는 경우 B 작업을 수행 할 때 작업 (예 : 변수에 대한 작성)의 실행 결과를 볼 수 있어야합니다.
이러한 상황에 대한 규칙에 대한 더 깊은 이해를 얻으려면 예를 들어 봅시다.
// 스레드 a와 b 객체 잠금으로 공유 된 코드 = new Object (); int a = 0; int b = 0; int c = 0; thread a, 다음 코드 동기화 된 (잠금) {a = 1; // 1 b = 2; // 2} // 3c = 3; // 4 // 스레드 B, 다음 코드를 호출하여 동기화 된 (잠금) {// 5 system.out.println (a); // 6 System.out.println (b); // 7 system.out.println (c); // 8}스레드 A가 먼저 실행되고 세 가지 변수 A, B 및 C에 각각 값을 할당한다고 가정합니다 (참고 : 변수 A, B의 할당은 동기 문장 블록에서 수행됩니다). 그런 다음 스레드 B가 다시 실행 되어이 세 가지 변수의 값을 읽고 인쇄합니다. 그렇다면 스레드 B에 의해 인쇄 된 변수 a, b 및 c의 값은 무엇입니까?
단일 스레딩 규칙에 따르면 스레드 A의 실행에서 2 개의 작업이 발생하기 전에 1 개의 작업이 발생하고 2 개의 작업이 3 개의 작업 전에 발생하며 3 개의 작업이 4 번의 작업 전에 발생하도록 할 수 있습니다. 마찬가지로 스레드 B 실행에서 6 개의 작업 전에 5 개의 작업이 발생하고 6 개의 작업이 7 개의 작업 전에 발생하며 8 개의 작업이 8 개의 작업 전에 발생합니다. 모니터의 잠금 해제 및 잠금 원칙에 따르면, 3 개의 작업 (잠금 해제 작업)은 5 개의 작업 (잠금 조작) 전에 발생합니다. 전이 규칙에 따르면, 우리는 작전 1과 2가 작전 6, 7 및 8 전에 발생한다고 결론을 내릴 수 있습니다.
전기의 메모리 의미론에 따르면, 작동 1과 2의 실행 결과는 작동 6, 7 및 8로 보이므로 스레드 B에서 A와 B는 1과 2이어야합니다. 작업 4 및 변수 c의 조작 8의 경우 1과 2입니다. 우리는 규칙 앞에 기존의 발생에 따라 작전 8 이전에 발생하는 작업 4를 추론 할 수 없습니다. 따라서 스레드 B에서 C에 액세스 된 변수는 여전히 3이 아닌 0 일 수 있습니다.