Java 스레드가 다른 스레드의 출력을 기다리는 방법?


128

application-logic-thread 및 데이터베이스 액세스 스레드로 Java 응용 프로그램을 만들고 있습니다. 둘 다 응용 프로그램의 전체 수명 동안 지속되며 둘 다 동시에 실행되어야합니다 (한 번은 서버와 대화하고 한 번은 사용자와 대화합니다. 앱이 완전히 시작되면 두 가지 모두 작동 해야 합니다 ).

그러나 시작할 때 앱 스레드가 DB 스레드가 준비 될 때까지 (현재는 사용자 정의 방법을 폴링하여 결정) 대기해야합니다 dbthread.isReady(). DB 스레드가 준비 될 때까지 앱 스레드가 차단되는지는 신경 쓰지 않습니다.

Thread.join() 솔루션처럼 보이지 않습니다. db 스레드는 앱 종료시에만 종료됩니다.

while (!dbthread.isReady()) {} 작동하지만 빈 루프는 많은 프로세서주기를 소비합니다.

다른 아이디어가 있습니까? 감사.

답변:


128

마법의 멀티 스레딩 세계에서 시작하기 전에 Sun의 Java Concurrency 와 같은 자습서를 살펴 보는 것이 좋습니다 .

또한 좋은 책들이 많이 있습니다 ( "Java 동시 프로그래밍", "실제 Java 동시성").

답을 얻으려면 :

를 기다려야하는 코드 dbThread에는 다음과 같은 내용이 있어야합니다.

//do some work
synchronized(objectYouNeedToLockOn){
    while (!dbThread.isReady()){
        objectYouNeedToLockOn.wait();
    }
}
//continue with work after dbThread is ready

dbThread의 방법으로 다음과 같이해야합니다.

//do db work
synchronized(objectYouNeedToLockOn){
    //set ready flag to true (so isReady returns true)
    ready = true;
    objectYouNeedToLockOn.notifyAll();
}
//end thread run method here

objectYouNeedToLockOn나는이 예제에서 사용하고는 각 스레드에서 동시에 조작해야하는 것이 바람직 객체, 또는 별도의를 만들 수 있습니다 Object(I 자체가 동기화 방법을 권하고 싶지 않다) 그 목적을 위해 :

private final Object lock = new Object();
//now use lock in your synchronized blocks

더 이해하기 위해 :
위와 같은 방법을 사용하는 다른 (때로는 더 나은) 방법이 있습니다. 예를 들어 CountdownLatchesJava 5 이후로 java.util.concurrent패키지와 하위 패키지 에는 많은 멋진 동시성 클래스가 있습니다. 동시성을 알거나 좋은 책을 얻으려면 온라인에서 자료를 찾아야합니다.


내가 틀리지 않으면 모든 스레드 코드를 객체에 잘 통합 할 수 없습니다. 따라서 객체 동기화를 사용하는 것이이 스레드 관련 작업을 함축시키는 좋은 방법이라고 생각하지 않습니다.
user1914692 2016 년

@ user1914692 : 위의 방법을 사용할 때 어떤 함정이 있는지 확실하지 않습니다.
Piskvor는 건물을 떠나

1
@Piskvor : 오래 전에 작성해서 죄송합니다. 제 생각에 거의 잊어 버렸습니다. 어쩌면 객체 동기화보다는 잠금을 사용하는 것이 더 좋았을 것입니다. 후자는 전자의 단순한 형태입니다.
user1914692

이것이 어떻게 작동하는지 이해하지 못합니다. 스레드가있는 경우 a에 오브젝트를 기다리고 synchronised(object)어떻게 다른 스레드를 통해 갈 수 synchronized(object)전화를 object.notifyAll()? 내 프로그램에서 모든 것이 방금 막혔습니다 synchronozed.
Tomáš Zato-Reinstate Monica

@ TomášZato 첫 번째 스레드는 object.wait()해당 객체의 잠금을 효과적으로 잠금 해제합니다. 두 번째 스레드가 동기화 된 블록을 "종료"하면 다른 오브젝트가 wait메소드 에서 해제 되고 해당 시점에서 잠금을 다시 얻습니다.
rogerdpack

140

카운터가 1 인 CountDownLatch 를 사용하십시오 .

CountDownLatch latch = new CountDownLatch(1);

이제 앱 스레드에서

latch.await();

DB 스레드에서 완료 후-

latch.countDown();

4
처음에는 코드의 의미를 이해하기가 더 어려울 수 있지만 그 솔루션은 단순하기 때문에 정말 마음에 듭니다.
치명적인 기타

3
이 기능을 사용하려면 걸쇠를 다 써야합니다. Windows에서 대기 가능 이벤트와 유사한 사용법을 얻으려면 BooleanLatch 또는 재설정 가능한 CountDownLatch를 시도해야합니다. docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 /…
phyatt

1
안녕하세요, 처음으로 이벤트를 발생시키는 비동기 메소드를 호출하면 : 1) asyncFunc (); 2) Latch.await (); 그런 다음 이벤트 처리 기능이 수신되면 카운트 다운합니다. Latch.await ()이 호출되기 전에 이벤트가 처리되지 않도록하려면 어떻게해야합니까? 1 행과 2 행 사이의 선점을 막고 싶습니다. 감사합니다.
NioX5199

1
오류가 발생하는 경우에 영원히 대기 방지하기 위해 넣어 countDown()으로 finally{}블록
다니엘 알더

23

요구 사항 ::

  1. 이전 스레드가 완료 될 때까지 다음 스레드의 실행을 기다립니다.
  2. 다음 스레드는 시간 소비에 관계없이 이전 스레드가 중지 될 때까지 시작해서는 안됩니다.
  3. 간단하고 사용하기 쉬워야합니다.

대답 ::

@ java.util.concurrent.Future.get () 문서를 참조하십시오.

future.get () 필요한 경우 계산이 완료되기를 기다린 다음 결과를 검색합니다.

작업 완료 !! 아래 예를 참조하십시오

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

public class ThreadTest {

    public void print(String m) {
        System.out.println(m);
    }

    public class One implements Callable<Integer> {

        public Integer call() throws Exception {
            print("One...");
            Thread.sleep(6000);
            print("One!!");
            return 100;
        }
    }

    public class Two implements Callable<String> {

        public String call() throws Exception {
            print("Two...");
            Thread.sleep(1000);
            print("Two!!");
            return "Done";
        }
    }

    public class Three implements Callable<Boolean> {

        public Boolean call() throws Exception {
            print("Three...");
            Thread.sleep(2000);
            print("Three!!");
            return true;
        }
    }

    /**
     * @See java.util.concurrent.Future.get() doc
     *      <p>
     *      Waits if necessary for the computation to complete, and then
     *      retrieves its result.
     */
    @Test
    public void poolRun() throws InterruptedException, ExecutionException {
        int n = 3;
        // Build a fixed number of thread pool
        ExecutorService pool = Executors.newFixedThreadPool(n);
        // Wait until One finishes it's task.
        pool.submit(new One()).get();
        // Wait until Two finishes it's task.
        pool.submit(new Two()).get();
        // Wait until Three finishes it's task.
        pool.submit(new Three()).get();
        pool.shutdown();
    }
}

이 프로그램의 출력 ::

One...
One!!
Two...
Two!!
Three...
Three!!

다른 스레드보다 큰 작업을 완료하기 전에 6 초가 걸린다는 것을 알 수 있습니다. 따라서 Future.get ()은 작업이 완료 될 때까지 기다립니다.

future.get ()을 사용하지 않으면 완료되기를 기다리지 않고 기반 시간 소비를 실행합니다.

Java 동시성에 대한 행운을 빕니다.


답변 주셔서 감사합니다! 나는 CountdownLatches를 사용 했지만 당신은 훨씬 더 유연한 접근 방식입니다.
Piskvor가 건물을 떠났습니다.

9

많은 정답이 있지만 간단한 예는 없습니다. 다음은 사용하는 쉽고 간단한 방법입니다 CountDownLatch.

//inside your currentThread.. lets call it Thread_Main
//1
final CountDownLatch latch = new CountDownLatch(1);

//2
// launch thread#2
new Thread(new Runnable() {
    @Override
    public void run() {
        //4
        //do your logic here in thread#2

        //then release the lock
        //5
        latch.countDown();
    }
}).start();

try {
    //3 this method will block the thread of latch untill its released later from thread#2
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//6
// You reach here after  latch.countDown() is called from thread#2

8
public class ThreadEvent {

    private final Object lock = new Object();

    public void signal() {
        synchronized (lock) {
            lock.notify();
        }
    }

    public void await() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }
}

이 클래스를 다음과 같이 사용하십시오.

ThreadEvent를 작성하십시오.

ThreadEvent resultsReady = new ThreadEvent();

이 방법에서 결과를 기다리고 있습니다.

resultsReady.await();

그리고 모든 결과가 생성 된 후 결과를 생성하는 방법에서 :

resultsReady.signal();

편집하다:

(이 게시물을 편집 해 주셔서 감사합니다. 그러나이 코드는 경쟁 조건이 매우 좋지 않으며 의견을 말할만한 평판이 없습니다.)

await () 이후에 signal ()이 호출되었다는 100 % 확신이있는 경우에만이를 사용할 수 있습니다. 이것이 Windows 이벤트와 같은 Java 객체를 사용할 수없는 가장 큰 이유입니다.

코드가 다음 순서로 실행되는 경우 :

Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();

다음 스레드 2는 영원히 기다릴 것이다 . Object.notify ()는 현재 실행중인 스레드 중 하나만 깨우기 때문입니다. 나중에 대기중인 스레드는 해제되지 않습니다. 이것은 a) 대기 또는 b) 명시 적으로 재설정 할 때까지 이벤트가 신호를받는 이벤트가 작동하는 방식과 매우 다릅니다.

참고 : 대부분의 경우 notifyAll ()을 사용해야하지만 이는 위의 "영원히 대기"문제와 관련이 없습니다.


7

패키지에서 CountDownLatch 클래스를 사용해보십시오.이 클래스 java.util.concurrent는 하위 수준보다 오류가 훨씬 적은 상위 수준의 동기화 메커니즘을 제공합니다.


6

두 스레드간에 공유 되는 Exchanger 개체를 사용하여 수행 할 수 있습니다 .

private Exchanger<String> myDataExchanger = new Exchanger<String>();

// Wait for thread's output
String data;
try {
  data = myDataExchanger.exchange("");
} catch (InterruptedException e1) {
  // Handle Exceptions
}

그리고 두 번째 실에서 :

try {
    myDataExchanger.exchange(data)
} catch (InterruptedException e) {

}

다른 사람들이 말했듯 이이 가벼운 마음으로 복사하여 붙여 넣기 코드를 사용하지 마십시오. 먼저 읽어보십시오.


4

미래 로부터 인터페이스java.lang.concurrent패키지 는 다른 스레드에서 계산 된 결과에 액세스 할 수 있도록 설계되었습니다.

이러한 종류의 작업을 수행 할 수있는 기성품 방법은 FutureTaskExecutorService 를 살펴보십시오 .

동시성 및 멀티 스레딩에 관심이있는 모든 사람에게 Java Concurrency In Practice 를 읽는 것이 좋습니다 . 분명히 Java에 집중하지만 다른 언어로 작업하는 사람에게는 많은 고기가 있습니다.


2

빠르고 더러운 것을 원한다면 while 루프 내에 Thread.sleep () 호출을 추가하면됩니다. 데이터베이스 라이브러리가 변경할 수없는 경우 다른 쉬운 해결책은 없습니다. 대기 시간이 준비 될 때까지 데이터베이스를 폴링해도 성능이 저하되지 않습니다.

while (!dbthread.isReady()) {
  Thread.sleep(250);
}

우아한 코드라고 할 수는 없지만 거의 작업이 완료됩니다.

데이터베이스 코드를 수정할 수있는 경우 다른 답변에서 제안한대로 뮤텍스를 사용하는 것이 좋습니다.


3
기다리는 것만으로도 바쁩니다. Java 5의 util.concurrent 패키지에서 구문을 사용하는 것이 좋습니다. stackoverflow.com/questions/289434/… 현재로서는 최고의 솔루션으로 보입니다.
Cem Catikkas

대기 중이지만이 특정 장소에서만 필요하고 DB 라이브러리에 액세스 할 수없는 경우 다른 작업을 수행 할 수 있습니까? 바쁜 대기가 반드시 악한 것은 아닙니다
Mario Ortegón

2

이것은 모든 언어에 적용됩니다 :

이벤트 / 리스너 모델을 원합니다. 특정 이벤트를 기다리는 리스너를 작성합니다. 이벤트는 워커 스레드에서 작성 (또는 신호)됩니다. 이렇게하면 현재 솔루션과 같이 조건이 충족되는지 확인하기 위해 지속적으로 폴링하는 대신 신호가 수신 될 때까지 스레드가 차단됩니다.

교착 상태의 가장 일반적인 원인 중 하나 인 상황은 발생한 오류에 관계없이 다른 스레드에 신호를 보내야합니다. 예-응용 프로그램에서 예외가 발생하고 메서드가 호출되어 다른 작업이 완료되었음을 알리지 않는 경우 이렇게하면 다른 스레드가 절대로 깨어나지 않습니다.

사건을 구현하기 전에이 패러다임을 더 잘 이해하기 위해 이벤트 및 이벤트 핸들러를 사용하는 개념을 살펴 보는 것이 좋습니다.

또는 mutex를 사용하여 블로킹 함수 호출을 사용할 수 있습니다. 이렇게하면 스레드가 리소스가 해제 될 때까지 대기하게됩니다. 이를 위해서는 다음과 같은 우수한 스레드 동기화가 필요합니다.

Thread-A Locks lock-a
Run thread-B
Thread-B waits for lock-a
Thread-A unlocks lock-a (causing Thread-B to continue)
Thread-A waits for lock-b 
Thread-B completes and unlocks lock-b

2

한 스레드의 차단 대기열에서 읽고 다른 스레드에 쓸 수 있습니다.


1

이후

  1. join() 배제되었습니다
  2. 이미 CountDownLatch를 사용 하고 있으며
  3. Future.get () 은 이미 다른 전문가들에 의해 제안되었습니다.

다른 대안을 고려할 수 있습니다.

  1. invokeAll을 에서ExecutorService

    invokeAll(Collection<? extends Callable<T>> tasks)

    주어진 작업을 실행하고 모든 완료시 상태 및 결과를 보유한 선물리스트를 반환합니다.

  2. ForkJoinPool 또는 newWorkStealingPool from Executors(Java 8 릴리스 이후)

    사용 가능한 모든 프로세서를 대상 병렬 처리 수준으로 사용하여 작업 스틸 링 스레드 풀을 만듭니다.


-1

여기에 이미지 설명을 입력하십시오

이 아이디어는 적용될 수 있습니까?. CountdownLatches 또는 Semaphores를 사용하면 완벽하게 작동하지만 인터뷰에 대한 가장 쉬운 답변을 찾고 있다면 이것이 적용될 수 있다고 생각합니다.


1
인터뷰는 괜찮지 만 실제 코드는 그렇지 않습니까?
Piskvor

이 경우 순차적으로 실행되기 때문에 다른 대기를 기다립니다. 여기에 주어진 가장 좋은 대답 인 CountdownLatches를 사용하면 스레드가 절대 절전 모드로 전환되지 않으므로 CPU주기를 사용하기 때문에 세마포어를 사용하는 것이 가장 좋습니다.
Franco

그러나 요점은 "순차적으로 실행"되지 않았습니다. GUI 스레드는 데이터베이스가 준비 될 때까지 기다린 다음 나머지 앱 실행 위해 동시에 실행됩니다. GUI 스레드는 명령을 DB 스레드로 보내고 결과를 읽습니다. (다시 : 인터뷰에서 사용할 수 있지만 실제 코드에서는 사용할 수없는 코드 포인트는 무엇입니까?) 내가 만난 대부분의 기술 면접관은 코드에 대한 배경 지식이 있고 동일한 질문을 할 것입니다. 나는 숙제를 겪는 것이 아니라 당시에 글을 쓰고있었습니다)
Piskvor

1
그래서. 이것은 세마포어를 사용하는 생산자 소비자 문제입니다. 나는 하나의 예를하려고 노력할 것이다
Franco

github.com/francoj22/SemProducerConsumer/blob/master/src/com/… 프로젝트를 만들었습니다 . 잘 작동합니다.
Franco
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.