JavaRush /Java Blog /Random-KO /스레드로 자바를 망칠 수는 없다: 2부 - 동기화
Viacheslav
레벨 3

스레드로 자바를 망칠 수는 없다: 2부 - 동기화

Random-KO 그룹에 게시되었습니다

소개

따라서 우리는 " 스레드로 Java를 망칠 수 없습니다: 1부 - 스레드 " 리뷰에서 읽을 수 있는 스레드가 Java에 있다는 것을 알고 있습니다 . 동시에 작업을 수행하려면 스레드가 필요합니다. 따라서 스레드가 어떻게든 서로 상호 작용할 가능성이 매우 높습니다. 이것이 어떻게 발생하는지, 그리고 어떤 기본 제어 기능이 있는지 이해해 봅시다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 1

생산하다

Thread.yield() 메서드 는 신비롭고 거의 사용되지 않습니다. 인터넷에는 설명에 대한 다양한 변형이 있습니다. 어떤 사람들은 우선 순위를 고려하여 스레드가 아래로 이동하는 일종의 스레드 대기열에 대해 글을 씁니다. 누군가는 스레드가 상태를 실행 중에서 실행 가능으로 변경할 것이라고 썼습니다(이러한 상태는 구분되지 않고 Java에서는 이를 구별하지 않지만). 그러나 실제로는 모든 것이 훨씬 더 알려지지 않았고 어떤 의미에서는 더 간단합니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 2메소드 문서 주제에는 " JDK-6416721: (spec thread) Fix Thread.yield() javadocyield " 버그가 있습니다 . 읽어보면 실제로 이 메소드는 이 스레드에 더 적은 실행 시간이 제공될 수 있다는 일부 권장 사항을 Java 스레드 스케줄러에 전달하는 것임이 분명합니다. 그러나 실제로 일어날 일, 스케줄러가 권장 사항을 들을지 여부 및 일반적으로 수행할 작업은 JVM 및 운영 체제의 구현에 따라 다릅니다. 아니면 다른 요인 때문일 수도 있습니다. 모든 혼란은 Java 언어 개발 중 멀티스레딩에 대한 재고로 인해 발생했을 가능성이 높습니다. " Java Thread.yield()에 대한 간략한 소개 " 리뷰에서 자세한 내용을 읽을 수 있습니다 . yield

수면 - 잠들기 스레드

스레드는 실행 중에 잠들 수 있습니다. 이는 다른 스레드와의 상호 작용의 가장 간단한 유형입니다. 자바 가상 머신이 설치되어 자바 코드가 실행되는 운영체제에는 스레드 스케줄러(Thread Scheduler)라는 자체 스레드 스케줄러가 있다. 언제 실행할 스레드를 결정하는 사람은 바로 그 사람입니다. 프로그래머는 Java 코드에서 이 스케줄러와 직접 상호 작용할 수 없지만 JVM을 통해 스케줄러에게 잠시 동안 스레드를 일시 중지하여 "잠자기 상태로 전환"하도록 요청할 수 있습니다. " Thread.sleep() " 및 " 멀티스레딩 작동 방식 " 기사에서 자세한 내용을 읽을 수 있습니다 . 또한 Windows OS에서 스레드가 작동하는 방식을 확인할 수 있습니다. " Windows 스레드 내부 ". 이제 우리는 그것을 우리 눈으로 직접 보게 될 것입니다. 다음 코드를 파일에 저장해 보겠습니다 HelloWorldApp.java.
class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Waked up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
보시다시피, 60초를 기다린 후 프로그램이 종료되는 작업이 있습니다. 우리는 컴파일 javac HelloWorldApp.java하고 실행합니다 java HelloWorldApp. 별도의 창에서 실행하는 것이 좋습니다. 예를 들어 Windows에서는 다음과 같습니다 start java HelloWorldApp. jps 명령을 사용하여 프로세스의 PID를 찾고 다음을 사용하여 스레드 목록을 엽니다 jvisualvm --openpid pidПроцесса. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 3보시다시피 스레드가 Sleeping 상태에 들어갔습니다. 실제로 현재 스레드를 휴면하는 것이 더 아름답게 수행될 수 있습니다.
try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Waked up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
우리가 모든 곳에서 처리한다는 사실을 눈치채셨나요 InterruptedException? 이유를 이해해 봅시다.

스레드 또는 Thread.interrupt 중단

문제는 스레드가 꿈에서 기다리는 동안 누군가가 이 대기를 중단하고 싶을 수도 있다는 것입니다. 이 경우에는 그러한 예외를 처리합니다. 이는 메소드가 Thread.stopDeprecated로 선언된 후에 수행되었습니다. 오래되어 사용하기에 바람직하지 않습니다. 그 이유는 메소드가 호출되면 stop스레드가 단순히 "종료"되어 예측할 수 없기 때문입니다. 흐름이 언제 중단될지 알 수 없었고 데이터의 일관성을 보장할 수도 없었습니다. 파일에 데이터를 쓰고 있는데 스트림이 파괴되었다고 상상해 보세요. 따라서 그들은 흐름을 죽이지 않고 흐름을 중단해야 한다고 알리는 것이 더 논리적이라고 결정했습니다. 이에 대응하는 방법은 흐름 자체에 달려 있습니다. 자세한 내용은 Oracle의 " Thread.stop이 더 이상 사용되지 않는 이유는 무엇입니까? " 에서 찾을 수 있습니다. 예를 살펴보겠습니다:
public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
이 예에서는 60초를 기다리지 않고 즉시 'Interrupted'를 인쇄합니다. 이는 스레드의 메소드를 호출했기 때문입니다 interrupt. 이 메소드는 "인터럽트 상태라는 내부 플래그"를 설정합니다. 즉, 각 스레드에는 직접 액세스할 수 없는 내부 플래그가 있습니다. 하지만 우리는 이 플래그와 상호 작용하는 기본 방법을 가지고 있습니다. 그러나 이것이 유일한 방법은 아닙니다. 스레드는 무언가를 기다리지 않고 단순히 작업을 수행하는 실행 과정에 있을 수 있습니다. 그러나 작업의 특정 지점에서 완료하기를 원할 수도 있습니다. 예를 들어:
public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			//Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
위의 예에서는 스레드 while가 외부에서 중단될 때까지 루프가 실행되는 것을 볼 수 있습니다. isInterrupted 플래그 에 대해 알아야 할 중요한 점은 이를 포착하면 InterruptedException플래그가 isInterrupted재설정되고 isInterruptedfalse를 반환한다는 것입니다. Thread 클래스에는 현재 스레드에만 적용되는 정적 메서드인 Thread.interrupted() 도 있지만 이 메서드는 플래그를 false로 재설정합니다! " 스레드 중단 " 장에서 자세한 내용을 읽을 수 있습니다 .

Join — 다른 스레드가 완료되기를 기다리는 중

가장 간단한 유형의 대기는 다른 스레드가 완료되기를 기다리는 것입니다.
public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
이 예에서는 새 스레드가 5초 동안 휴면 상태로 유지됩니다. 동시에 메인 스레드는 잠자는 스레드가 깨어나 작업을 마칠 때까지 기다립니다. JVisualVM을 통해 살펴보면 스레드의 상태는 다음과 같습니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 4모니터링 도구 덕분에 스레드에서 무슨 일이 일어나고 있는지 확인할 수 있습니다. 이 메소드는 호출된 스레드가 살아있는 동안 join실행되는 Java 코드가 포함된 단순한 메소드이기 때문에 매우 간단합니다 . wait스레드가 종료되면(종료 시) 대기가 종료됩니다. 이것이 바로 이 방법의 마법입니다 join. 따라서 가장 흥미로운 부분으로 넘어 갑시다.

컨셉 모니터

멀티스레딩에는 모니터라는 것이 있습니다. 일반적으로 모니터라는 단어는 라틴어에서 "감독자" 또는 "감독자"로 번역됩니다. 이 기사의 틀 내에서 우리는 본질을 기억하려고 노력할 것이며, 원하는 분들은 링크에서 자료를 자세히 살펴보시기 바랍니다. Java 언어 사양, 즉 JLS: " 17.1. 동기화 "로 여행을 시작해 보겠습니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 5스레드 간의 동기화를 위해 Java는 "모니터"라는 특정 메커니즘을 사용하는 것으로 나타났습니다 . 각 개체에는 연결된 모니터가 있으며 스레드는 이를 잠그거나 잠금 해제할 수 있습니다. 다음으로 Oracle 웹사이트에서 " 내장 잠금 및 동기화 " 교육 튜토리얼을 찾아보겠습니다 . 이 튜토리얼에서는 Java의 동기화가 내장 잠금 또는 모니터 잠금으로 알려진 내부 엔터티를 중심으로 구축된다는 점을 설명합니다. 이러한 잠금을 간단히 "모니터"라고 부르는 경우가 많습니다. 또한 Java의 모든 객체에는 이와 관련된 고유 잠금이 있음을 다시 확인합니다. " Java - 내장 잠금 및 동기화 "를 읽을 수 있습니다 . 다음으로, Java의 개체가 모니터와 연결될 수 있는 방법을 이해하는 것이 중요합니다. Java의 각 객체에는 프로그래머가 코드에서 사용할 수 없지만 가상 머신이 객체를 올바르게 작동하는 데 필요한 일종의 내부 메타데이터인 헤더가 있습니다. 개체 헤더에는 다음과 같은 MarkWord가 포함되어 있습니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Habr의 기사는 여기에서 매우 유용합니다: " 멀티스레딩은 어떻게 작동합니까? 1부: 동기화 ." 이 기사에서는 JDK bugtaker의 작업 블록 요약 " JDK-8183909 "에 대한 설명을 추가할 가치가 있습니다. " JEP-8183909 " 에서도 같은 내용을 읽을 수 있습니다 . 따라서 Java에서는 모니터가 개체와 연결되어 있으며 스레드가 이 스레드를 차단하거나 "잠금 가져오기"라고 말할 수도 있습니다. 가장 간단한 예:
public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
따라서 키워드를 사용하면 synchronized현재 스레드(이러한 코드 줄이 실행됨)는 개체와 연결된 모니터를 사용하고 object"잠금 가져오기" 또는 "모니터 캡처"를 시도합니다(두 번째 옵션이 더 바람직함). 모니터에 대한 경합이 없는 경우(즉, 동일한 개체에 대한 동기화를 원하는 사람이 없는 경우) Java는 "편향된 잠금"이라는 최적화를 수행하려고 시도할 수 있습니다. Mark Word의 개체 제목에는 해당 태그와 모니터가 연결된 스레드에 대한 기록이 포함됩니다. 이렇게 하면 모니터를 캡처할 때 오버헤드가 줄어듭니다. 모니터가 이전에 이미 다른 스레드에 연결되어 있었다면 이 잠금만으로는 충분하지 않습니다. JVM은 다음 잠금 유형인 기본 잠금으로 전환합니다. CAS(비교 및 교환) 작업을 사용합니다. 동시에 Mark Word의 헤더는 더 이상 Mark Word 자체를 저장하지 않지만 저장소에 대한 링크와 태그가 변경되어 JVM이 기본 잠금을 사용하고 있음을 이해할 수 있습니다. 여러 스레드의 모니터에 대한 경합이 있는 경우(하나는 모니터를 캡처했고 두 번째는 모니터가 해제되기를 기다리고 있음) Mark Word의 태그가 변경되고 Mark Word는 모니터에 대한 참조를 다음과 같이 저장하기 시작합니다. 객체 - JVM의 일부 내부 엔터티입니다. JEP에 명시된 대로 이 경우 이 엔터티를 저장하려면 기본 힙 메모리 영역에 공간이 필요합니다. 이 내부 엔터티의 저장 위치에 대한 링크는 Mark Word 개체에 위치합니다. 따라서 우리가 볼 수 있듯이 모니터는 실제로 공유 리소스에 대한 여러 스레드의 액세스 동기화를 보장하는 메커니즘입니다. JVM이 전환하는 이 메커니즘의 여러 구현이 있습니다. 따라서 단순화를 위해 모니터에 대해 이야기할 때 실제로는 잠금에 대해 이야기하고 있습니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 7

동기화되었으며 잠금으로 대기 중

이전에 살펴본 것처럼 모니터의 개념은 "동기화 블록"(또는 임계 섹션이라고도 함)의 개념과 밀접한 관련이 있습니다. 예를 살펴보겠습니다:
public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized (lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized (lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print("  " + i);
		}
		System.out.println(" ...");
	}
}
여기서 메인 스레드는 먼저 작업을 새 스레드로 보낸 다음 즉시 잠금을 "캡처"하고 이를 사용하여 긴 작업(8초)을 수행합니다. 이 모든 시간 동안 작업은 실행을 위해 블록에 들어갈 수 없습니다 synchronized. 잠금이 이미 사용 중입니다. 스레드가 잠금을 얻을 수 없으면 모니터에서 이를 기다립니다. 이를 수신하자마자 실행이 계속됩니다. 스레드가 모니터를 벗어나면 잠금이 해제됩니다. JVisualVM에서는 다음과 같습니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 8보시다시피 스레드가 차단되어 모니터를 점유할 수 없기 때문에 JVisualVM의 상태를 "모니터"라고 합니다. 코드에서 스레드 상태를 확인할 수도 있지만 이 상태의 이름은 JVisualVM 용어와 유사하지만 일치하지 않습니다. 이 경우 th1.getState()루프는 BLOCKED 를for 반환합니다 . 루프가 실행되는 동안 모니터는 스레드에 의해 점유되고 스레드는 차단되어 잠금이 반환될 때까지 작업을 계속할 수 없습니다. 동기화 블록 외에도 전체 메소드를 동기화할 수 있습니다. 예를 들어 클래스의 메서드는 다음과 같습니다 . lockmainth1HashTable
public synchronized int size() {
	return count;
}
한 단위 시간 내에 이 메서드는 하나의 스레드에 의해서만 실행됩니다. 하지만 우리에겐 자물쇠가 필요해요, 그렇죠? 네, 필요해요. 객체 메소드의 경우 잠금은 입니다 this. 이 주제에 대한 흥미로운 토론이 있습니다. " 동기화 블록 대신 동기화 방법을 사용하면 이점이 있습니까? " 메소드가 정적이면 잠금은 this(정적 메소드의 경우는 불가능하므로 this) 클래스 객체(예: Integer.class)가 됩니다.

모니터 앞에서 기다리세요. 통지 및 통지 All 메소드

스레드에는 모니터에 연결된 또 다른 대기 방법이 있습니다. sleep및 와 달리 join그냥 호출할 수 없습니다. 그리고 그의 이름은 입니다 wait. wait이 메서드는 우리가 기다리고 싶은 모니터의 개체에서 실행됩니다 . 예를 살펴보겠습니다:
public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // task будет ждать, пока его не оповестят через lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // После оповещения нас мы будем ждать, пока сможем взять лок
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // Ждём и после этого забираем себе лок, оповещаем и отдаём лок
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
JVisualVM에서는 다음과 같습니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 10이것이 어떻게 작동하는지 이해하려면 메소드가 를 wait참조한다는 notify점 을 기억해야 합니다 java.lang.Object. 스레드 관련 메소드가 Object. 그러나 여기에 답이 있습니다. 우리가 기억하는 것처럼 Java의 모든 객체에는 헤더가 있습니다. 헤더에는 모니터 정보(잠금 상태에 대한 데이터)를 포함한 다양한 서비스 정보가 포함되어 있습니다. 그리고 우리가 기억하는 것처럼 각 객체(즉, 각 인스턴스)는 모니터라고도 불리는 내장 잠금이라고 하는 내부 JVM 엔터티와 연관되어 있습니다. 위의 예에서 작업은 와 연결된 모니터에 동기화 블록을 입력하는 것을 설명합니다 lock. 이 모니터에 대한 잠금을 얻을 수 있으면 wait. 이 작업을 실행하는 스레드는 모니터를 해제 lock하지만 모니터에서 알림을 기다리는 스레드 대기열에 합류하게 됩니다 lock. 이 스레드 대기열을 WAIT-SET이라고 하며 본질을 더 정확하게 반영합니다. 대기열보다 세트에 가깝습니다. 스레드는 main작업 작업으로 새 스레드를 생성하고 이를 시작한 후 3초 동안 기다립니다. 이를 통해 높은 확률로 새 스레드가 스레드보다 먼저 잠금을 잡고 main모니터에 대기할 수 있습니다. 그런 다음 스레드 main자체가 동기화 블록에 들어가 lock모니터에 스레드 알림을 수행합니다. 알림이 전송된 후 스레드는 main모니터를 해제하고 lock, 이전에 기다리고 있던 새 스레드는 lock모니터가 해제될 때까지 기다린 후 계속 실행됩니다. 스레드 중 하나에만 알림을 보낼 수도 notify있고( ) 대기열에 있는 모든 스레드에 동시에 알림을 보낼 수도 있습니다( notifyAll). " Java에서 inform()과 informAll()의 차이점 " 에서 자세한 내용을 읽을 수 있습니다 . 알림 순서는 JVM 구현에 따라 다르다는 점에 유의하는 것이 중요합니다. " Notify 및 Notifyall을 사용하여 기아를 해결하는 방법은 무엇입니까? " 에서 자세한 내용을 읽을 수 있습니다 . 개체를 지정하지 않고도 동기화를 수행할 수 있습니다. 이는 별도의 코드 섹션이 동기화되지 않고 전체 메소드가 동기화될 때 수행될 수 있습니다. 예를 들어 정적 메소드의 경우 잠금은 클래스 객체( 를 통해 획득 .class)가 됩니다.
public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
잠금 사용 측면에서 두 방법 모두 동일합니다. instance메서드가 정적이 아닌 경우 현재 , 즉 에 따라 동기화가 수행됩니다 this. 그건 그렇고, 앞서 우리는 이 메소드를 사용하여 getState스레드의 상태를 얻을 수 있다고 말했습니다. 따라서 여기에 모니터에 의해 대기열에 추가된 스레드가 있습니다. 메소드가 wait대기 시간 제한을 지정한 경우 상태는 WAITING 또는 TIMED_WAITING이 됩니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 11

스레드의 수명주기

우리가 본 것처럼 흐름은 삶의 과정에서 그 상태를 바꿉니다. 본질적으로 이러한 변경 사항은 스레드의 수명 주기입니다. 스레드가 방금 생성되면 NEW 상태가 됩니다. 이 위치에서는 아직 시작되지 않았으며 Java 스레드 스케줄러는 아직 새 스레드에 대해 아무것도 모릅니다. 스레드 스케줄러가 스레드에 대해 알기 위해서는 thread.start(). 그러면 스레드는 RUNNABLE 상태가 됩니다. 인터넷에는 Runnable 상태와 Running 상태가 분리되어 있는 잘못된 구성표가 많이 있습니다. 하지만 이건 실수이기 때문에... Java는 "실행 준비" 상태와 "실행 중" 상태를 구분하지 않습니다. 스레드가 살아 있지만 활성 상태가 아닌 경우(실행 가능하지 않은 경우) 다음 두 가지 상태 중 하나입니다.
  • BLOCKED - 보호된 섹션으로의 진입을 기다립니다. synchonized블록 에 .
  • WAITING - 조건에 따라 다른 스레드를 기다립니다. 조건이 true이면 스레드 스케줄러가 스레드를 시작합니다.
스레드가 시간별로 대기 중인 경우 TIMED_WAITING 상태입니다. 스레드가 더 이상 실행되지 않으면(성공적으로 완료되었거나 예외가 발생한 경우) TERMINATED 상태가 됩니다. 스레드의 상태(해당 상태)를 알아내기 위해 메소드가 사용됩니다 getState. isAlive스레드에는 스레드가 종료되지 않은 경우 true를 반환하는 메서드도 있습니다 .

LockSupport 및 스레드 파킹

Java 1.6부터 LockSupport 라는 흥미로운 메커니즘이 있었습니다 . 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 12이 클래스는 "허가" 또는 권한을 이를 사용하는 각 스레드와 연결합니다. 메서드 호출은 park허가가 사용 가능한 경우 즉시 반환되어 호출 중에 동일한 허가를 차지합니다. 그렇지 않으면 차단됩니다. 메소드를 호출하면 unpark허가가 아직 제공되지 않은 경우 허가가 가능해집니다. Permit은 1개뿐입니다. Java API에서는 LockSupport특정 Semaphore. 간단한 예를 살펴보겠습니다.
import java.util.concurrent.Semaphore;
public class HelloWorldApp{

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Просим разрешение и ждём, пока не получим его
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
이제 세마포어에 허가가 0개 있으므로 이 코드는 영원히 대기합니다. 그리고 코드에서 호출되면 acquire(즉, 권한 요청) 스레드는 권한을 받을 때까지 기다립니다. 기다리고 있으므로 처리해야합니다 InterruptedException. 흥미롭게도 세마포어는 별도의 스레드 상태를 구현합니다. JVisualVM을 보면 상태가 Wait가 아니라 Park임을 알 수 있습니다. 스레드로 Java를 망칠 수는 없습니다: 2부 - 동기화 - 13또 다른 예를 살펴보겠습니다.
public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            //Запаркуем текущий поток
            System.err.println("Will be Parked");
            LockSupport.park();
            // Как только нас распаркуют - начнём действовать
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());

        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
스레드 상태는 WAITING이지만 JVisualVM은 waitfrom synchronizedparkfrom을 구별합니다 LockSupport. 이것이 왜 그렇게 중요합니까 LockSupport? 다시 Java API로 돌아가서 Thread State WAITING을 살펴보겠습니다 . 보시다시피 들어가는 방법은 세 가지뿐입니다. 2가지 방법 - 이것 waitjoin. 그리고 세 번째는 입니다 LockSupport. Java의 잠금은 동일한 원칙에 따라 구축되었으며 LockSupport더 높은 수준의 도구를 나타냅니다. 하나를 사용해 보도록 하겠습니다. 예를 들어 다음을 살펴보겠습니다 ReentrantLock.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
이전 예와 마찬가지로 여기서는 모든 것이 간단합니다. lock누군가가 리소스를 공개할 때까지 기다립니다. JVisualVM을 살펴보면 main스레드가 잠금을 부여할 때까지 새 스레드가 파킹되는 것을 볼 수 있습니다. 잠금에 대한 자세한 내용은 " Java 8의 다중 스레드 프로그래밍. 2부. 변경 가능한 객체에 대한 액세스 동기화 " 및 " Java Lock API. 사용 이론 및 예 "에서 확인할 수 있습니다. 잠금 구현을 더 잘 이해하려면 " Phaser Class " 개요에서 Phazer에 대해 읽어 보는 것이 좋습니다. 그리고 다양한 동기화 장치에 대해 이야기하려면 Habré " Java.util.concurrent.* 동기화 장치 참조 " 에 대한 기사를 읽어야 합니다 .

이번 리뷰에서는 스레드가 Java에서 상호 작용하는 주요 방식을 살펴보았습니다. 추가 자료: #비아체슬라프
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION