야드 농 왕 - 라인 통신
8983 단어 스레드 통신
스레드 통신의 목표는 스레드 간에 서로 신호를 보낼 수 있도록 하는 것이다.다른 한편, 스레드 통신은 스레드로 하여금 다른 스레드의 신호를 기다릴 수 있게 한다.
예를 들어 스레드 B는 스레드 A의 신호를 기다릴 수 있는데 이 신호는 스레드 B 데이터가 준비되어 있음을 알릴 것이다.이게 동기화 과정이에요.
공유 객체를 통한 통신
스레드 간에 신호를 보내는 간단한 방법은 공유 대상의 변수에 신호 값을 설정하는 것이다.루틴 A는 동기화 블록에 boolean형 구성원 변수hasDataToProcess를true로 설정하고 루틴 B도 동기화 블록에서hasDataToProcess라는 구성원 변수를 읽는다.이 간단한 예는 신호를 가진 대상을 사용하고 set과 check 방법을 제공합니다.
public class MySignal{
protected boolean hasDataToProcess = false;
public synchronized boolean hasDataToProcess(){
return this.hasDataToProcess;
}
public synchronized void setHasDataToProcess(boolean hasData){
this.hasDataToProcess = hasData;
}
}
스레드 A와 B는 통신을 위해 MySignal 공유 실례를 가리키는 인용을 받아야 합니다.처리해야 할 데이터는 공유 캐시에 저장할 수 있다.
대기 중
처리 데이터를 준비하는 스레드 B가 데이터를 사용할 수 있도록 기다리고 있습니다.다시 말하면, 이것은 라인 A의 신호를 기다리고 있으며, 이 신호는hasDataToProcess ()를true로 되돌려줍니다.루틴 B는 이 신호를 기다리기 위해 순환에서 운행한다
protected MySignal sharedSignal = ...
...
while(!sharedSignal.hasDataToProcess()){
//do nothing... busy waiting
}
wait(), notify() 및 notifyAll()
평균 대기 시간이 매우 짧지 않으면 대기 라인을 실행하는 CPU를 효과적으로 사용할 수 없습니다.그렇지 않으면 대기 라인이 대기 신호를 받을 때까지 수면이나 비운행 상태로 들어가는 것이 현명하다.자바에는 신호가 기다리고 있을 때 라인이 비운행 상태로 바뀌도록 내장된 대기 메커니즘이 있습니다.java.lang.object 클래스는 이 대기 메커니즘을 실현하기 위해 wait (), notify (), notify All () 세 가지 방법을 정의했다.
한 라인이 임의의 대상의wait () 방법을 호출하면 다른 라인이 같은 대상의 notify () 방법을 호출할 때까지 비운행 상태가 됩니다.wait () 또는 notify () 를 호출하기 위해서는 먼저 그 대상의 자물쇠를 가져와야 합니다.즉, 라인은 동기화 블록에서wait () 또는 notify () 를 호출해야 한다.
이 아래에 아무것도 없는 클래스를 사용했습니다. 구분 기준으로 synchronized를 호출하면 다른 대상 자물쇠를 얻을 수 있습니다.있고 단 한 색!
public class MonitorObject{
}
public class MyWaitNotify{
MonitorObject myMonitorObject = new MonitorObject();
public void doWait(){
synchronized(myMonitorObject){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
}
public void doNotify(){
synchronized(myMonitorObject){
myMonitorObject.notify();
}
}
}
대기 스레드는 DoWait () 를 호출하고, 깨우기 스레드는 DoNotify () 를 호출합니다.한 라인이 대상의 notify () 방법을 호출하면, 대상의 모든 라인에서 한 라인이 깨어나 실행될 수 있도록 기다립니다. (교정: 이 깨어날 라인은 무작위입니다. 어느 라인을 깨울지 지정할 수 없습니다.)주어진 대상을 기다리고 있는 모든 루틴을 깨우는 notify All () 방법도 제공합니다.
보시다시피, 대기 라인이든 깨우기 라인이든 동기화 블록에서wait () 와 notify () 를 호출합니다.이것은 강제적이다!대상 자물쇠가 없으면 wait (), notify (), notify All () 을 호출할 수 없습니다.그렇지 않으면 Illegal Monitor State Exception 예외가 발생합니다.
그런데 그럴 리가 있겠어요?동기화 블록에서 스레드가 실행되기를 기다리는 동안 모니터 대상(my Monitor 대상)의 자물쇠를 가지고 있지 않습니까?대기 스레드가 DoNotify () 에 들어오는 동기화 블록을 막을 수 없습니까?답은: 그럴 수는 없다.라인이wait () 방법을 호출하면, 모니터 대상에 있는 자물쇠를 방출합니다.이것은 다른 라인도wait () 또는 notify () 를 호출할 수 있도록 허용합니다.
한 라인이 깨어나면, notify () 를 호출한 라인이 자신의 동기화 블록을 종료할 때까지wait () 방법을 사용할 수 없습니다.다시 말하면 깨어난 라인은 모니터 대상의 자물쇠를 다시 가져와야만 웨이트 () 방법 호출을 종료할 수 있다. 왜냐하면 웨이트 방법 호출은 동기화 블록에서 실행되기 때문이다.여러 개의 루틴이 notify All () 에서 깨어나면, 같은 시간에 한 개의 루틴만 wait () 방법을 종료할 수 있습니다. 루틴마다 wait () 를 종료하기 전에 모니터 대상의 자물쇠를 가져와야 하기 때문입니다.(only one 종료 가능)
손실된 신호(Missed Signals)
notify () 와 notify All () 방법은 호출된 방법을 저장하지 않습니다. 이 두 방법이 호출되었을 때, 대기 상태가 없을 수도 있기 때문입니다.통지 신호가 지나간 후 버려졌다.따라서, 알림 라인이wait () 를 사용하기 전에 notify () 를 호출하면 기다리는 라인이 이 신호를 놓치게 됩니다.그게 문제가 아닐 수도 있고.그러나 어떤 경우, 이것은 대기 라인을 영원히 기다리고, 다시 깨어나지 못하게 할 수도 있다. 왜냐하면, 라인이 깨우는 신호를 놓쳤기 때문이다.
신호를 잃어버리지 않도록 신호류에 저장해야 한다.MyWaitNotify의 예에서 알림 신호는 MyWaitNotify 실례의 구성원 변수에 저장되어야 한다.MyWaitNotify 수정 버전은 다음과 같습니다.
public class MyWaitNotify2{
MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false;
public void doWait(){
synchronized(myMonitorObject){
if(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
}
public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}
doNotify () 방법은 notify () 를 호출하기 전에wasSignalled 변수를true로 설정하는 것을 유의하십시오.또한, doWait() 방법은 wait()를 호출하기 전에wasSignalled 변수를 검사합니다.실제로 이전 DoWait() 호출과 이번 DoWait() 호출 사이의 시간대에 신호가 수신되지 않으면 wait()만 호출됩니다.신호가 분실되는 것을 피하기 위해서, 통지되었는지 여부를 변수로 저장합니다.notify 전에 알림을 받은 자신을 설정합니다.wait 후 자신이 알림을 받지 않았음을 설정하고 알림을 기다려야 합니다.
가짜 깨우기
알 수 없는 이유로, 루틴은 notify () 와 notify All () 을 호출하지 않은 상태에서 깨어날 수 있습니다.이른바 가짜 깨우기(spurious wakeups)다.까닭 없이 깨어나다.가짜 깨우기를 방지하기 위해서, 신호를 저장하는 구성원 변수는if 표현식이 아니라while 순환에서 검사를 받을 것입니다.이런while 순환을 자전거 자물쇠라고 한다. (교정: 이런 방법은 신중해야 한다. 현재의 JVM이 자전거 순환을 실현하면 CPU가 소모된다. 만약에doNotify 방법을 사용하지 않으면 DoWait 방법은 계속 자전거 순환을 하고 CPU는 너무 많이 소모된다).깨어난 라인은 자물쇠 (while 순환) 의 조건이false로 바뀔 때까지 자전한다.다음 MyWaitNotify2의 수정 버전은 이 점을 보여 준다.
public class MyWaitNotify3{
MonitorObject myMonitorObject = new MonitorObject();
boolean wasSignalled = false;
public void doWait(){
synchronized(myMonitorObject){
while(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
}
public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}
여러 개의 스레드가 같은 신호를 기다리다
만약 여러 개의 라인이 기다리고 있다면, notify All () 에서 깨어나지만, 하나만 계속 실행할 수 있습니다.while 순환을 사용하는 것도 좋은 방법입니다.매번 하나의 라인만 모니터 대상 자물쇠를 얻을 수 있습니다. 이것은 wait () 호출을 종료하고wasSignalled 로고를 제거할 수 있음을 의미합니다. (false로 설정)이 스레드가 DoWait () 의 동기화 블록을 종료하면 다른 스레드는wait () 호출을 종료하고 while 순환에서wasSignalled 변수 값을 검사합니다.그러나 이 표지는 첫 번째 깨어난 라인에 의해 제거되었기 때문에 나머지 깨어난 라인은 다음 신호가 올 때까지 대기 상태로 돌아갈 것이다.
wait ()/notify () 메커니즘에서 전역 대상, 문자열 상수 등을 사용하지 마십시오.대응하는 유일한 대상을 사용해야 한다.예를 들어, MyWaitNotify 3의 각 인스턴스에는 빈 문자열에서 wait()/notify()를 호출하는 대신 자체 모니터 객체가 있습니다.이게 유일성이야!!!MyWaitNotify 객체가 여러 개 있을 경우 모니터 객체로 빈 문자열을 사용하고 JVM에서 동일한 위치를 가리키면 끝입니다~ 예상치 못한 일이 발생할 수 있습니다
파이프라인(영어: 모니터, 모니터라고도 부른다)은 여러 개의 작업 라인에서 공유 자원에 대한 상호 배척 접근을 실현하는 대상이나 모듈이다.이러한 공유 자원은 일반적으로 하드웨어 설비나 변수이다.파이프라인은 최대 한 라인만 서브루틴을 실행하는 시간대에 실현되었다.데이터 구조를 수정하여 상호 배척 접근을 실현하는 병행 프로그램 설계에 비해 관리 절차는 어느 정도 프로그램 설계를 간소화시켰다.
http://ifeve.com/thread-signaling/