본문 바로가기

Java 공부

12장 스레드풀(4)

작업 완료순으로 통보 받기

-작업 요청 순서대로 작업 처리가 완료되는 것은 아니다.

-작업의 양과 스레드 스케쥴링에 따라서 먼저 요청한 작업이 나중에 완료되는 경우도 발생한다.

-여러 개의 작업들이 순차적으로 처리될 필요성이 없고, 처리 결과도 순차적으로 이용할 필요가 없다면

      --> 작업 처리가 완료된 것부터 결과를 얻어 이용하는 것이 좋다.

-스레드 풀에서 작업 처리가 완료된 것만 통보 받는 방법

-CompletionService는 처리 완료된 작업을 가져오는 poll()과 take()메소드를 제공한다.

리턴타입 

메소드명(매개변수) 

설명 

 Future<V>

 poll()

 완료된 작업의 Future를 가져옴.

 완료된 작업이 없다면 즉시 null을 리턴함

 Future<V>

 poll( long timeout, TimeUnit unit)

 완료된 작업의 Future를 가져옴.

 완료된 작업이 없다면 timeout까지 블로킹

 Future<V>

 take()

 완료된 작업의 Future를 가져옴.

 완료된 작업이 없다면 있을 때까지 블로킹

 Future<V>

 submit(Callable<V> task)

 스레드 풀에 Callable 작업 처리 요청 

 --> ExecutorService에도 정의되어 있는 내용인데 

      CompletionService에도 정의되어 있는 것

 Future<V> submit(Runnable task, V result)

 스레드 풀에 Runnable 작업 처리 요청


-CompletionService 객체 얻기

ExecutorService executorService = Executors.newFixedThreadPool(

Runtime.getRuntime().availableProcessors()

);

CompletionService<V> completionService = new ExecutorCompletionService<V>(

executorService

);

  -<V>는 타입 파라미터로 스레드 풀이 처리하고 난 후의 결과값의 타입을 말한다. 


-작업 처리 요청 방법

-poll()과 take()메소드를 이용해서 처리 완료된 작업의 Future을 얻으려면

-CompletionService의 submit()메소드로 작업 처리 요청을 해야 한다.

completionService.submit(Callable<V> task);            //1

completionService.submit(Runnable task, V result);    //2   

  *ExcutorService의 submit메소드가 아니라 completionService의 submit메소드를 이용해서 작업을 넣어줘야

poll과 take메소드를 사용할 수 있는 것이다.

  1) completionService에 submit을 이용해서 Callable을 작업큐에 넣어준다.

  2) 1)과 같이 submit을 이용해서 Runnable(결과값이 있는 작업객체)을 작업큐에 넣어주는데, 외부객체에 결과값을 지정한다는 뜻


예제를 통해 알아보자.


public class CompletionServiceExample {

public static void main(String[] args) {

ExecutorService executorService = Executors.newFixedThreadPool(

Runtime.getRuntime().availableProcessors()

);

CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executorService);

System.out.println("작업 처리 요청");

//---------------------------------  1

for(int i=1; i<=3; i++) {

completionService.submit(new Callable<Integer>() {


@Override

public Integer call() throws Exception {

int sum = 0;

for(int i=1; i<=10; i++) {

sum += i;

}

return sum;

}

});

}

System.out.println("처리 완료된 작업 확인");


//---------------------------------  2

executorService.submit(new Runnable() {

@Override

public void run() {

while(true) {

try {

//---------------------------------  3

Future<Integer> future = completionService.take();

int value = future.get();   

System.out.println("처리결과: "+ value);

} catch (Exception e) {

break;

   }

}

}

});

//---------------------------------  4

try {

Thread.sleep(3000);

} catch (InterruptedException e) {}

  //---------------------------------  5

executorService.shutdownNow();


}


}


*보충 설명

1)  작업 객체를 만들고, submit메소드를 통해서 작업 큐에 작업을 넣고 스레드에게 작업 요청을 한다. X3

     이때, completionService객체를 통한 작업요청임을 명심해야 한다.


2)  executorService.submit(Runnable객체)

     runnable객체에 정의된 작업내용을 작업 큐에 넣고, 스레드에게 작업을 요청한다는 뜻

즉 처리 완료된 작업을 확인할 때에도 하나의 작업으로 만들고, 스레드풀에 넣어서 확인하겠다는 뜻이다.

왜일까?

completionService.take()메소드를 사용하기 위해서이다

take()메소드는 블로킹이 된다 

따라서 take()를 호출하는 스레드가 UI를 생성하거나 변경, 또는 이벤트를 처리하는 스레드라면 블로킹 되어서 리턴이 안되면

      UI작업을 할 수가 없기 때문에 take()라는 메소드는 항상 다른 스레드에서 실행되어야 한다.

따라서 executorService를 이용해서 스레드가 take를 호출해서 완료된 작업이 있을 때마다 가져와서 처리할 수 있도록

      하나의 다른 스레드에서 독립적으로 실행할 수 있도록 하는 것이다.


3)  int value = future.get();  여기서 get은 블로킹 되지 않는다. 

    왜? take() 라는 메소드가 이미 작업이 완료된 결과 값을 가져오기 때문에 get()은 그 즉시 결과 값을 리턴해준다.


4)   스레드를 3초동안 일시 정지 하는이유?

completionService에 Callable객체를 작업 큐에 넣고, 

스레드 풀에서 작업을 처리 하도록 할건데, 

이때 스레드가 작업 큐에서 작업을 가져와서 처리를 하려면 시간이 필요하다.

따라서 그 시간을 벌기 위해서 3초동안 일시정지한 것이다.


5)  3초 동안에도 처리가 되지 않으면 강제적으로 스레드 풀을 종료


콜백방식의 작업완료 통보 받기

콜백이란?

-애플리케이션이 스레드에게 작업 처리를 요청한 후, 다른 기능을 수행할 동안,

-스레드가 작업을 완료하면 애플리케이션의 메소드를 자동 실행하는 기법을 말한다.

-이 때 자동 실행되는 메소드를 콜백 메소드라고 한다.


작업완료 통보 얻기 : 블로킹 vs 콜백 이해

블로킹 방식 : Future객체를 사용한다. Future.get()메소드로 스레드가 작업을 모두 처리할 동안 메인스레드는 기다리는 상태에 있게 된다.(블로킹)

            스레드가 작업 처리가 끝나면 메인 스레드는 작업의 결과를 받아서 그 다음 코드를 실행한다


콜백 방식 : 메인 스레드가 작업처리 요청을 하게 되면 submit()메소드의 리턴값으로 Future객체가 리턴되지만 이 객체를 사용하진 않는다. 

     작업처리 요청만 하고, 메인스레드는 자신의 코드를 계속 실행한다. 스레드도 요청받은 작업을 실행한다.

     스레드가 작업을 모두 처리하면 자동적으로 콜백메소드를 실행해서 콜백메소드에서 이 작업을 이용할 수 있도록 한다.


콜백 객체와 콜백 하기

-콜백 객체 : 콜백 메소드를 가지고 있는 객체

*java.nio.channels.CompletionHandler 인터페이스 활용해서 만들 수도 있다.

CompletionHandler<V, A> callback = new CompletionHandler<V, A>() {

@Override

public void completed(V result, A attachment) {

}

@Override

public void failed(Throwable exc, A attachment {

}

};

V : 스레드가 작업을 처리한 결과 값의 타입

A :  콜백메소드에서 사용할 수 있는 첨부 객체의 타입

completed(V result, A attachment) : 처리를 완료했을 때 실행하는 메소드 

failed(Throwable exc, A attachment) : 처리하다가 실패했을 경우 실행하는 메소드 

    Throwable는 예외, A는 첨부객체


-콜백하기 : 스레드에서 콜백 객체의 메소드 호출

Runnable task = new Runnable() {

@Override

public void run() {

try{

//작업 처리

V result = ...;

callback.completed(result, null);

} catch(Exception e) {

     callback.failed(e, null);

}

}

};

-run메소드에서 작업을 진행하다가 작업이 끝났을 때, callback객체의 completed메소드를 호출하여 콜백메소드가 실행을 하도록 하여 결과를

애플리케이션에서 이용할 수 있도록 한다.

-여기서 중요한 것은 콜백메소드를 실행하는 것은 메인스레드가 아니라 스레드풀의 스레드이다.


예제를 통해 알아보자.


public class CallbackExample {


private ExecutorService executorService;    //스레드풀 선언

public CallbackExample() {                    //생성자

executorService = Executors.newFixedThreadPool(

Runtime.getRuntime().availableProcessors()

);

}

//콜백 객체 생성 ------------------------1

private CompletionHandler<Integer,Void> callback = 

new CompletionHandler<Integer,Void>() {


@Override

public void completed(Integer result, Void attachment) {

System.out.println("completed() 실행 "+ result);

}


@Override

public void failed(Throwable exc, Void attachment) {

System.out.println("failed() 실행 "+ exc.toString());

}

};

public void doWork(String x, String y) {

Runnable task = new Runnable() {

@Override

public void run() {

try {

int intX = Integer.parseInt(x);

int intY = Integer.parseInt(y);

int result = intX + intY;

callback.completed(result, null);

}catch(NumberFormatException e) {

callback.failed(e, null);

}

}

};

executorService.submit(task);

}

public void finish() {

executorService.shutdown();

}

public static void main(String[] args) {

CallbackExample example = new CallbackExample();

example.doWork("3", "3");

example.doWork("3", "삼");

example.finish();

}


}


*보충설명

1) 콜백객체를 생성한다. 콜백객체는 인터페이스타입, 제네릭 타입이기도 하다.

결과 타입(스레드가 작업을 처리한 이후의 결과타입)으로 Integer, 첨부타입으로 Void를 지정해준다   *Void : 첨부타입이 없다는 뜻 






본 포스팅은 이것이 자바다 책을 참고하여 작성하였습니다.

'Java 공부' 카테고리의 다른 글

13장 제네릭(Generic) (2)_멀티 타입 파라미터  (0) 2019.05.22
13장 제네릭(Generic)  (0) 2019.01.20
12장 스레드풀(3)  (0) 2018.12.11
12장 스레드풀(2)  (0) 2018.12.11
12장 스레드풀(1)  (0) 2018.12.09