스레드 커뮤니케이션의 목표는 스레드가 서로 신호를 보내도록하는 것입니다. 반면에 스레드 통신을 사용하면 스레드가 다른 스레드의 신호를 기다릴 수 있습니다.
공유 객체를 통한 커뮤니케이션
스레드 사이에 신호를 보내는 쉬운 방법은 공유 객체의 변수에서 신호 값을 설정하는 것입니다. 스레드 A는 부울 멤버 변수 hasdatatoprocess를 동기화 블록에서 true로 설정하고 스레드 B는 동기화 블록의 hasdatatoprocess 멤버 변수를 읽습니다. 이 간단한 예제는 신호를 보유하는 객체를 사용하고 세트 및 점검 방법을 제공합니다.
공개 클래스 MySignal {Protected Boolean hasdatatoprocess = false; public synchronized boolean hasdatatoprocess () {return this.hasdataToprocess; } public synchronized void sethasdatatoprocess (boolean hasdata) {this.hasdataToprocess = hasdata; }}스레드 A와 B는 커뮤니케이션을위한 MySignal 공유 인스턴스에 대한 참조를 얻어야합니다. 참고 문헌이 다른 Mysingal 인스턴스를 가리키면 서로의 신호를 감지 할 수 없습니다. 처리 할 데이터는 공유 캐시 영역에 저장 될 수 있으며, 이는 MySignal 인스턴스와 별도로 저장됩니다.
바쁘다
데이터 처리를 준비하는 스레드 B는 데이터를 사용할 수 있기를 기다리고 있습니다. 다시 말해, hasdatatoprocess ()가 true를 반환하게하는 스레드 A의 신호를 기다리고 있습니다. 스레드 B는이 신호를 기다리기 위해 루프에서 실행됩니다.
보호 된 MySignal SharedSignal = ...... while (! sharedSignal.hasdataToprocess ()) {// 아무것도 ... 바쁘다}대기 (), notify () 및 notifyall ()
바쁜 대기는 평균 대기 시간이 매우 짧지 않으면 대기 스레드를 실행하는 CPU를 효과적으로 활용하지 않습니다. 그렇지 않으면 대기 스레드를 잠을 자거나 대기중인 신호를받을 때까지 실행되지 않도록하는 것이 현명합니다.
Java는 신호를 기다리는 동안 스레드가 실행되지 않을 수 있도록 내장 대기 메커니즘을 가지고 있습니다. java.lang.object 클래스는이 대기 메커니즘을 구현하기 위해 세 가지 메소드 인 Wait (), notify () 및 notifyall ()을 정의합니다.
스레드가 모든 객체의 Wait () 메소드를 호출하면 다른 스레드가 동일한 객체의 notify () 메소드를 호출 할 때까지 실행이 아닌 상태가됩니다. 대기 () 또는 notify ()를 호출하려면 스레드는 먼저 해당 객체의 잠금을 얻어야합니다. 즉, 스레드는 동기화 블록에서 Wait () 또는 notify ()를 호출해야합니다. 다음은 mysingal -mywaitnotify의 수정 된 버전입니다.
public class moniterobject {} public class mywaitnotify {monitorObject myMonitorObject = new monitorObject (); public void dowait () {synchronized (myMonitorObject) {try {myMonitorObject.wait (); } catch (InterpruptedException e) {... ...}}} public void donotify () {synchronized (myMonitorObject) {myMonitorObject.Notify (); }}}대기 스레드는 Dowait ()을 호출하고 Wake-Up 스레드는 donotify ()를 호출합니다. 스레드가 객체의 notify () 메소드를 호출하면 객체를 기다리는 스레드 중 하나가 깨어나고 실행할 수 있습니다 (참고 : 깨어날 스레드는 무작위이며 어떤 스레드가 깨어날 스레드를 지정할 수 없습니다). 주어진 객체를 기다리는 모든 스레드를 깨우는 notifyall () 메소드도 제공됩니다.
보시다시피 대기 스레드 또는 웨이크 업 스레드이든 동기화 블록에서 Wait () 및 notify ()를 호출합니다. 이것은 필수입니다! 스레드가 객체 잠금을 고정하지 않으면 대기 (), notify () 또는 notifyall ()을 호출 할 수 없습니다. 그렇지 않으면, 불법 모니터 스테이트 예외 예외가 발생합니다.
(참고 : 이것은 JVM이 구현되는 방식입니다. 대기를 호출 할 때 먼저 현재 스레드가 잠금의 소유자인지 확인하고 불법 모니터 스테이트 엑스크를 던졌습니다.)
그러나 이것이 어떻게 가능합니까? 스레드가 동기화 블록에서 실행되기를 기다릴 때 항상 모니터 객체 (Mymonitor Object)의 잠금을 고정하지 않습니까? 대기 스레드가 동기식 블록으로 들어가는 웨이크 업 스레드가 Donotify ()를 차단할 수 있습니까? 대답은 : 사실입니다. 스레드가 대기 () 메소드를 호출하면 고정 모니터 객체의 잠금을 출시합니다. 이렇게하면 다른 스레드가 Wait () 또는 notify ()를 호출 할 수 있습니다.
스레드가 깨어나면 대기 () 메소드 호출은 notify ()가 호출 될 때까지 즉시 종료 할 수 없습니다.
공개 클래스 mywaitnotify2 {monitorObject myMonitorObject = new MonitorObject (); 부울이 signaled = false; public void dowait () {synchronized (myMonitorObject) {if (! wasSignaled) {try {myMonitorObject.wait (); } catch (InterruptedException e) {...}} // 신호를 지우고 계속 실행합니다. signaled = false; }} public void donotify () {synchronized (mymonitorObject) {wasSignaled = true; mymonitorObject.notify (); }}}
스레드는 자체 동기화 블록을 종료합니다. 다시 말해, Waken 스레드는 대기 메소드 호출이 동기화 블록에서 실행되므로 Wait () 메소드 호출을 종료하기 전에 모니터 객체의 잠금을 되 찾아야합니다. Notifyall ()에 의해 여러 스레드가 깨어나면 동시에 하나의 스레드만이 Wait () 메소드를 종료 할 수 있습니다. 각 스레드는 Wait ()를 종료하기 전에 모니터 객체의 잠금을 얻어야하기 때문입니다.
누락 된 신호
notify () 및 notifyall () 메소드는이 두 가지 메소드가 호출되면 대기 상태에 스레드가 없을 수 있기 때문에 호출하는 메소드를 저장하지 않습니다. 알림 신호가 폐기되었습니다. 따라서 대기 ()을 호출하기 전에 알림을 받기 전에 스레드 호출 ()가 전화를 걸면 대기 스레드는이 신호를 누락합니다. 이것은 문제가 될 수도 있고 아닐 수도 있습니다. 그러나 경우에 따라 스레드가 웨이크 업 신호를 놓치기 때문에 대기 스레드가 항상 기다리고 더 이상 일어나지 않을 수 있습니다.
신호를 잃지 않으려면 신호 클래스에 저장해야합니다. MyWaitNotify 예제에서, 알림 신호는 MyWaitNotify 인스턴스의 멤버 변수에 저장되어야합니다. 다음은 MyWaitNotify의 수정 된 버전입니다.
공개 클래스 mywaitnotify2 {monitorObject myMonitorObject = new MonitorObject (); 부울이 signaled = false; public void dowait () {synchronized (myMonitorObject) {if (! wasSignaled) {try {myMonitorObject.wait (); } catch (InterruptedException e) {...}} // 신호를 지우고 계속 실행합니다. signaled = false; }} public void donotify () {synchronized (mymonitorObject) {wasSignaled = true; mymonitorObject.notify (); }}}donotify () 메소드는 notify ()를 호출하기 전에 wassignalled 변수를 true로 설정합니다. 동시에 Dowait () 메소드는 WASSIGNALED 변수를 확인하기 전에 ()를 호출합니다. 실제로, 이전 Dowait () 호출 과이 Dowait () 호출 사이의 기간 동안 신호가 수신되지 않으면 Wait () 만 호출합니다.
(증거 참고 : 신호 손실을 피하기 위해 변수를 사용하여 통지되었는지 여부를 저장하십시오. 알림을 받기 전에 알림을 받으십시오. 대기 한 후에는 알림을받지 않고 알림을 기다릴 필요가 있습니다.)
가짜 일어나
어떤 이유로 든 Notify () 및 notifyall ()을 호출하지 않고 스레드가 깨어날 수 있습니다. 이것을 스퓨리어스 웨이크 업이라고합니다. 아무 이유없이 일어나십시오.
MyWaitNotify2의 Dowait () 메소드에서 잘못된 모닝이 발생하면 대기 스레드는 올바른 신호를받지 않더라도 후속 작업을 수행 할 수 있습니다. 이로 인해 응용 프로그램에 심각한 문제가 발생할 수 있습니다.
오 탐용을 방지하기 위해 신호를 보유하는 멤버 변수는 IF 표현식이 아닌 WIDE 루프로 점검합니다. 이러한 기간 루프는 스핀 잠금이라고합니다 (참고 :이 접근법은 신중해야합니다. 현재 JVM 구현 스핀은 CPU를 소비합니다. Donotify 방법이 오랫동안 요구되지 않으면 Dowait 방법이 연속적으로 회전하고 CPU가 너무 많이 소비됩니다). 깨어 난 스레드는 스핀 잠금 장치 (루프)의 조건이 거짓이 될 때까지 회전합니다. mywaitnotify2의 다음 수정 된 버전은 다음을 보여줍니다.
공개 클래스 mywaitnotify3 {monitorObject myMonitorObject = new monitorObject (); 부울이 signaled = false; public void dowait () {synchronized (myMonitorObject) {while (! wasSignaled) {try {myMonitorObject.wait (); } catch (InterruptedException e) {...}} // 신호를 지우고 계속 실행합니다. signaled = false; }} public void donotify () {synchronized (mymonitorObject) {wasSignaled = true; mymonitorObject.notify (); }}}대기 () 메소드는 IF 표현식이 아닌 while 루프에 있습니다. 대기 스레드가 신호를받지 않고 깨어나면, 서명 된 변수가 허위가되고, where 루프가 다시 실행되어 웨이크 업 스레드가 대기 상태로 돌아 오도록 촉구합니다.
여러 스레드가 동일한 신호를 기다리고 있습니다
여러 스레드가 대기 중이고 NotifyAll ()에 의해 깨어나지 만 하나만 실행할 수있는 경우 While Loop을 사용하는 것도 좋은 방법입니다. 하나의 스레드만이 매번 모니터 객체 잠금을 가져올 수 있습니다. 즉, 하나의 스레드만이 대기 () 호출을 종료하고 Wassignaled 플래그 (False로 설정)를 지울 수 있습니다. 이 스레드가 Dowait () 동기화 블록을 종료하면 다른 스레드가 Wait () 호출을 종료하고 WASSIGNALED 변수 값을 while 루프에서 확인합니다. 그러나이 깃발은 첫 번째 깨어 난 스레드에 의해 지워 졌으므로 나머지 깨어 난 스레드는 다음에 신호가 도착할 때까지 대기 상태로 돌아갑니다.
문자열 상수 또는 글로벌 객체에서 Wait ()를 호출하지 마십시오.
(증거 참고 :이 장에서 언급 된 문자열 상수는 일정 값의 변수를 나타냅니다)
이 기사의 이전 버전은 MyWaitNotify 예제의 파이프 객체로 문자열 상수 ( "")를 사용합니다. 예는 다음과 같습니다.
공개 클래스 myWaitNotify {String myMonitorObject = ""; 부울이 signaled = false; public void dowait () {synchronized (myMonitorObject) {while (! wasSignaled) {try {myMonitorObject.wait (); } catch (InterruptedException e) {...}} // 신호를 지우고 계속 실행합니다. signaled = false; }} public void donotify () {synchronized (mymonitorObject) {wasSignaled = true; mymonitorObject.notify (); }}}빈 문자열의 동기화 블록에서 Wait () 및 notife ()를 호출하여 발생하는 문제는 잠금 (또는 다른 상수 문자열)으로 JVM/컴파일러가 상수 문자열을 동일한 객체로 변환한다는 것입니다. 이것은 2 개의 다른 mywaitnotify 인스턴스가 있더라도 동일한 빈 문자열 인스턴스를 참조합니다. 또한 첫 번째 MyWaitNotify 인스턴스에서 Dowait ()을 호출하는 스레드가 두 번째 MyWaitNotify 인스턴스에서 donotify ()를 호출하는 스레드에 의해 깨어날 위험이 있음을 의미합니다. 이 상황은 다음과 같이 그릴 수 있습니다.
처음에는 이것이 큰 문제가 아닐 수도 있습니다. 결국, 두 번째 mywaitnotify 인스턴스에서 donotify ()가 호출되면 실제로 발생하는 것은 스레드 A와 B가 잘못 깨어났다는 것입니다. 모닝 스레드 (a 또는 b)는 while 루프의 신호 값을 확인한 다음 대기 상태로 돌아갑니다. donotify ()가 첫 번째 mywaitnotify 인스턴스에서 호출되지 않기 때문에 대기하는 인스턴스입니다. 이 상황은 잘못된 각성을 유발하는 것과 같습니다. 스레드 A 또는 B는 신호 값이 업데이트되지 않고 깨어납니다. 그러나 코드는이 상황을 처리하므로 스레드는 대기 상태로 돌아갑니다. 4 개의 스레드가 동일한 공유 문자열 인스턴스에서 4 개의 스레드가 대기 ()를 호출하고 ()를 호출하더라도 Dowait () 및 donotify ()의 신호는 각각 2 개의 mywaitnotify 인스턴스에 의해 저장됩니다. mywaitnotify1에 대한 donotify () 호출은 mywaitnotify2의 실을 깨울 수 있지만 신호 값은 mywaitnotify1에만 저장됩니다.
문제는 donotify ()가 notifyall () 대신 notify () 만 호출하기 때문에 동일한 문자열 (빈 문자열) 인스턴스에 4 개의 스레드가 있더라도 하나의 스레드 만 깨어납니다. 따라서, 스레드 A 또는 B가 C 또는 D로 전송 된 신호로 깨어나면 자체 신호 값을 확인하여 신호가 수신되는지 확인한 다음 대기 상태로 돌아갑니다. C 또는 D는 실제로받은 신호 값을 확인하기 위해 깨어나지 않았으므로 신호가 손실되었습니다. 이 상황은 위에서 언급 한 신호 누락 문제와 동일합니다. C와 D는 신호로 전송되었지만 신호에 응답 할 수는 없습니다.
donotify () 메소드가 notify () 대신 notifyall ()을 호출하면 모든 대기 스레드가 깨어나고 신호 값이 차례로 점검됩니다. 스레드 A와 B는 대기 상태로 돌아 오지만 C 또는 D의 스레드는 신호를 알리고 Dowait () 메소드 호출을 종료합니다. 신호를 얻은 스레드가 Dowait ()를 종료하는 과정에서 신호 값 (False로 설정)을 지우기 때문에 C 또는 D의 다른 하나는 대기 상태로 돌아갑니다.
위의 단락을 읽은 후에는 notify () 대신 notifyall ()을 사용하려고 시도 할 수 있지만 이는 성능 기반 나쁜 아이디어입니다. 하나의 스레드 만 신호에 응답 할 수있는 경우 매번 모든 스레드를 깨울 이유가 없습니다.
따라서 : Wait ()/notify () 메커니즘에서는 전역 객체, 문자열 상수 등을 사용하지 마십시오. 해당 고유 한 객체를 사용해야합니다. 예를 들어, MyWaitNotify3의 각 인스턴스에는 빈 문자열에서 Wait ()/notify ()를 호출하는 대신 자체 모니터 객체가 있습니다.
위는 Java 멀티 스레딩 및 스레드 커뮤니케이션에 대한 정보입니다. 우리는 향후 관련 정보를 계속 추가 할 것입니다. 이 사이트를 지원 해주셔서 감사합니다!