저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

안드로이드에서 비동기로 작업을 할 때 사용되는 AsyncTask는 잘 사용하지 않으면, 코드 유지보수를 어렵게 만드는 악의 원천이 될 수 있다. 특히, 많은 안드로이드 입문서들이  AsyncTask를 이용해서 비동기를 처리하는 코드를 보여줄 때 다음의 두 가지 문제점을 포함하고 있는 경우가 많다.

  • AsyncTask를 상속 받은 클래스가 비동기 처리 결과를 사용하는 코드에 대한 의존을 갖는다.
  • 비동기 처리가 필요한 기능마다 AsyncTask 클래스를 상속받아 구현한다.
우선, 첫 번째 문제는 코드의 응집도를 낮추고 결합도를 높여주기 때문에, 코드가 만들어지면 만들어질수록 유지보수를 하기 어렵게 만들어준다. 다음은 전형적인 코드이다.

public class AsyncHttpTask extends AsyncTask<String, Void, String> {
private Handler handler;
private Exception exception;

public AsyncHttpTask(Handler handler) {
this.handler = handler;
}

@Override
protected T doInBackground(String... urls) {
try {
// urls[0]의 URL부터 데이터를 읽어와 String으로 리턴
...
return responseData;
} catch(Exception ex) {
this.exception = ex;
return null;
}
}

@Override
protected void onPostExecute(String responseData) {
if (exception != null) {
Message msg = handler.obtainMessage();
msg.what = -1;
msg.obj = exception;
handler.sendMessage(msg);
return;
} else {
Message msg = handler.obtainMessage();
msg.what = 0;
msg.obj = responseData;
handler.sendMessage(msg);
}
}
}


AsyncHttpTask를 필요로 하는 코드는 Handler 객체를 이용해서 결과를 받아온다.


public class SomeActivity extends Activity {


public void some() {

new AsyncHttpTask(handler).execute(url);

}

private Handler handler = new Handler() {

public void handleMessage(Message msg) {

switch(msg.what) {

case -1:

// 에러 처리

break;

case 0:

// 정상 응답 처리

break;

}

}

};


}


AsyncHttpTask를 사용하는 모든 코드는 위와 같이 결과를 받아오기 위해 Handler를 사용해야 하고, -1과 0 이라는 숫자를 사용해야 한다. (-1과 0 대신에 ASYNC_HTTP_ERROR, ASYNC_HTTP_OK 라는 상수를 사용하면 조금은 나을 것 같다.) 문제는 Handler를 이곳 저곳에서 사용하기 시작하면, case 문이 점점 복잡해질 수 있다는 것이다. 예를 들어, 또 다른 Async 처리 기능이 있다고 해 보자. 이 경우 위 코드는 다음과 같이 될 것이다.


public class SomeActivity extends Activity {


public void some() {

new AsyncHttpTask(handler).execute(url);

}

public void process() {

new AsyncImageFileProcessingTask(handler).execute(file);

}

private Handler handler = new Handler() {

public void handleMessage(Message msg) {

switch(msg.what) {

case -1:

// 에러 처리

break;

case 0:

// 정상 응답 처리

break;

case 101:

// 파일 프로세싱 시작

break;

case 102:

// 파일 프로세싱 종료

break;

}

}

};


}


여기서 짜증나는 건 AsyncHttpTask와 AsyncImageFileProcessingTask가 동일한 Handler를 공유할 수 있기 때문에, 서로 Message의 what 값이 충돌나지 않게 구현해야 한다는 점이다. AsyncImageFileProcessingTask가 실패할 경우 what 값을 -1로 준다면, 위 코드는 문제가 발생한다. 즉, 서로 다른 비동기 처리 코드임에도 불구하고 Handler를 사용함으로써 암묵적인 커플링이 발생하는 것이다.


그래서, Handler를 사용해서 결과를 주고 받는 방식보다는 콜백 인터페이스를 이용해서 결과를 받는 것이 코드 유지보수에 유리하다. (이에 대한 내용은 예전에 쓴  '안드로이드 Handler 사용으로 인해 흩어진 코드 커맨드와 콜백으로 정리하기' 글을 참고한다.)


유지보수를 고려한 비동기 처리 기반 코드


최근에 안드로이드 코딩 중, 다른 종류의 비동기 작업을 해야할 일이 생겨서, 위에서 언급한 문제를 최대한 제거한 비동기 처리 기반 코드를 만들어보았다. 이 코드에서 출현하는 타입은 세 개이다.

  • AsyncExecutor<T> - AsyncTask를 상속받은 클래스로서, 비동기로 작업을 실행하고 결과를 전달해주는 기능이다.
  • Callable<T> - java.util.concurrent.Callable 인터페이스로 비동기로 실행할 작업을 제공한다.
  • AsyncCallback<T> - 비동기 처리 결과를 받을 때 사용되는 콜백 인터페이스이다.
  • AsyncExecutorAware<T> - 콜백 구현체가 AsyncExecutor 객체를 참조해야 할 경우에 사용되는 인터페이스이다.
AsyncCallback 인터페이스

AsyncCallback 인터페이스는 다음과 같이 정의하였다.

public interface AsyncCallback<T> {
    public void onResult(T result);

    public void exceptionOccured(Exception e);

    public void cancelled();
}

각 메서드는 결과를 받고(onResult), 처리 도중 발생한 익셉션을 받고(exceptionOccured), 작업을 취소했음을 받을(cancelled) 때 사용된다. 필요에 따라 진행율을 받을 수 있는 콜백 메서드를 추가할 수도 있을 것이다.

AsyncExecutorAware 인터페이스

콜백 구현 객체에서 AsyncExecutor에 대한 접근이 필요할 때가 있는데, 이런 경우 콜백 구현 클래스는 AsyncExecutorAware 인터페이스를 구현하면 된다.

public interface AsyncExecutorAware<T> {

public void setAsyncExecutor(AsyncExecutor<T> asyncExecutor);

}

뒤에서 구현할 AsyncExecutor는 콜백 객체가 AsyncExecutorAware 인터페이스를 구현한 경우, 콜백 객체의 setAsyncExecutor()를 호출하여 자기 자신을 콜백 객체에 전달한다.

AsyncExecutor 클래스

AsyncExecutor는 다음과 같이 구현하였다.

import java.util.concurrent.Callable;

import android.os.AsyncTask;
import android.util.Log;

public class AsyncExecutor<T> extends AsyncTask<Void, Void, T> {
private static final String TAG = "AsyncExecutor";

private AsyncCallback<T> callback;
private Callable<T> callable;
private Exception occuredException;

public AsyncExecutor<T> setCallable(Callable<T> callable) {
this.callable = callable;
return this;
}

public AsyncExecutor<T> setCallback(AsyncCallback<T> callback) {
this.callback = callback;
processAsyncExecutorAware(callback);
return this;
}

@SuppressWarnings("unchecked")
private void processAsyncExecutorAware(AsyncCallback<T> callback) {
if (callback instanceof AsyncExecutorAware) {
((AsyncExecutorAware<T>) callback).setAsyncExecutor(this);
}
}

@Override
protected T doInBackground(Void... params) {
try {
return callable.call();
} catch (Exception ex) {
Log.e(TAG,
"exception occured while doing in background: "
+ ex.getMessage(), ex);
this.occuredException = ex;
return null;
}
}

@Override
protected void onPostExecute(T result) {
if (isCancelled()) {
notifyCanceled();
}
if (isExceptionOccured()) {
notifyException();
return;
}
notifyResult(result);
}

private void notifyCanceled() {
if (callback != null)
callback.cancelled();
}

private boolean isExceptionOccured() {
return occuredException != null;
}

private void notifyException() {
if (callback != null)
callback.exceptionOccured(occuredException);
}

private void notifyResult(T result) {
if (callback != null)
callback.onResult(result);
}

}

AsyncExecutor의 주요 기능은 다음과 같다.
  • doInBackground() 메서드: callable 객체에 작업 실행을 위임한다.
  • onPostExecute() 및 관련 메서드: callback 객체에 결과를 전달한다.
사용 예시

AsyncExecutor의 사용 방법은 다음과 같다.

public void load() {
// 비동기로 실행될 코드
Callable<ToonDataList> callable = new Callable<ToonDataList>() {
@Override
public ToonDataList call() throws Exception {
return client.getFeaturedTopData(startRow);
}
};

new AsyncExecutor<ToonDataList>()
.setCallable(callable)
.setCallback(callback)
.execute();
}

// 비동기로 실행된 결과를 받아 처리하는 코드
private AsyncCallback<ToonDataList> callback = new AsyncCallback<ToonDataList>() {
@Override
public void onResult(ToonDataList result) {
appendResult(result);
}

@Override
public void exceptionOccured(Exception e) {
AlertUtil.alert(context, context.getString(R.string.dataloading_error));
}

@Override
public void cancelled() {
}
};


기타

AsyncCallback 인터페이스의 두 메서드 중 한 개만 구현하면 되는 클래스를 위해 아무것도 하지 않는 구현을 제공한 Base 클래스를 추가했다.

public interface AsyncCallback<T> {
...
public static abstract class Base<T> implements AsyncCallback<T> {

@Override
public void exceptionOccured(Exception e) {
}

@Override
public void cancelled() {
}

}
}

관련자료


Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 뉴비뉴비 2013.05.14 11:29 신고  댓글주소  수정/삭제  댓글쓰기

    아 너무 좋네요 코드가 완전 깔끔해졌어요~ 잘보고갑니다 감사합니다 ^ 0^

  2. 식빵 2013.07.23 16:39 신고  댓글주소  수정/삭제  댓글쓰기

    정말 신세계를 보고 가네요..=ㅂ=!!!

  3. 대관령 2014.04.08 22:19 신고  댓글주소  수정/삭제  댓글쓰기

    감동의 연속입니다.
    제가 몇일동안 고민하던 사항들을 한방에 날려주시는군요.
    한편으로는 새로운 자극을 받게되어 감사드립니다.
    화이팅입니다~~

  4. 비동기 2014.07.03 13:43 신고  댓글주소  수정/삭제  댓글쓰기

    좋은 글 감사합니다^^

    궁금한게 있는데요.

    한 화면에서 여러가지 비동기 처리를 할 때는 작업의 구분을 어떻게 해야 할까요?

    AsyncCallback 함수의 onResult 에서 처리를 하려면 Handler에서 처리하는 것 처럼 상수 값이나 다른 구분값이 필요할 것 같은데요...

    아니면 AsyncCallback 를 비동기 처리 갯수만큼 만들어야 하나요?

    제가 제대로 이해를 못한건지.. 답변 부탁드릴게요 ㅠ

  5. 오리왕 2014.07.06 09:14 신고  댓글주소  수정/삭제  댓글쓰기

    callable 내에서 진행률을 처리해야 할 경우, AsyncTask 의 progress 즉 진행률을 비동기로 받으려면 어떻게 해야 하나요?
    제가 생각한 구조는 아래의 코드와 같습니다. 이렇게 된다면 callable 의 call() 내부에서 callback 을 호출하는 부분(callback.onProgress) 이 필요한데요...
    이 부분의 구현에 대한 힌트 또는 예제 코드를 얻을수 있을까요?

    public interface AsyncCallback<T> {
    public void onResult(T result);
    public void onProgress(Integer progress); // 진행률 표시와 관련된 부분
    public void exceptionOccured(Exception e);
    public void cancelled();
    }

    doInBackground(Void... params) {
    try {
    // 진행률 표시부 시작
    int progress = 10;
    if (callback != null)
    callback.onProgress(progress);
    ...
    // 진행률 표시부 종료
    return callable.call();

    } catch (Exception e) {
    }
    }

    • 최범균 madvirus 2014.07.08 10:12 신고  댓글주소  수정/삭제

      말씀하신 것처럼 AsyncCallback 에는 onProgress 메서드를 추가해야 할 것 같고, 진행률을 누가 전달할지가 관건일 듯 합니다.
      실제 진행률을 알고 있는 건, callable 이지 AsyncExecutor는 아니니까요. AsyncExecutor가 AsyncTask를 상속받았으니까, 결과적으로
      - callable에서 AsyncExecutor 객체의 publishProgress()를 실행하고,
      - AsyncExecutor 객체의 onProgressUpdate()에서 callback의 onProgress()를 호출하도록 하면 될 것 같습니다.

  6. beetcom 2014.08.01 02:14 신고  댓글주소  수정/삭제  댓글쓰기

    callable에서 AsyncExecutor aware 하더라도 publishProgress는 protected 니까
    callable이 publishProgress를 직접 호출할 수는 없을 것 같은데요.
    뭔가 깔끔한 방법은 안떠오르네요.

    • 최범균 madvirus 2014.08.04 19:06 신고  댓글주소  수정/삭제

      글을 쓴지 좀 되서 기억을 복구하는데 꽤 시간이 걸리네요 ^^; (한해 한해 기억력이 뚝뚝 떨어짐을 느낍니다.)
      조금 더 나가보자면 다음과 같이 해 볼 수 있을 것 같습니다.
      * AsyncExecutor를 인터페이스로 만들고,
      * AsyncExecutor 인터페이스에 notifyProgress()와 같은 메서드를 추가하고,
      * AsyncExecutor를 구현한 AsyncTaskExecutor 클래스가 AsyncTask 클래스를 상속받도록 한 다음에
      * AsyncTaskExecutor 클래스의 notifyProgress() 메서드가 상위 클래스의 publishProgress()를 호출하게 한다거나 하는 식으로
      해 보면 조금 쓸만한 모양이 나오지 않을까 싶습니다. 해보지 않고 상상으로만 적어본거라 어떨지 모르겠네요.

  7. 김형주 2014.09.04 12:01 신고  댓글주소  수정/삭제  댓글쓰기

    자료 감사합니다.

    설명해주신 자료를 보면 AsynCallback으로 try/catch 의 Exception 관련 에러메시지는 받을 수 있지만,
    HttpResponse Status Code (ex: 404, 500 .. ) 에 관련된 에러 코드는 받을 수 없는것 같습니다.

    원하는 코드까지 받으려면 AsyncHttpTask 에서 리턴되는 결과값을 HashMap으로 대체해서
    응답값과 코드까지 받으려고 하는데.. 문제가 될까요?


  8. sinki 2015.01.08 17:12 신고  댓글주소  수정/삭제  댓글쓰기

    우선 정말 좋은 예제 감사드립니다.

    상기 예제에서 정말 이해가 안가는 부분이 있는데
    AsyncExecutorAware 인터페이스 에서

    "콜백 구현 객체에서 AsyncExecutor에 대한 접근이 필요할 때가 있는데"
    실제 어떻게 구현 해야 하는지 감 조차 안 옵니다. ^^

    많이 바쁘시겠지만, 힌트라도 주시겠어요?

    미리 감사합니다.


    • 최범균 madvirus 2015.01.12 12:25 신고  댓글주소  수정/삭제

      클래스가 두 개 인터페이스를 상속받도록 구현하시면 됩니다.

      public class MyCallback implements AsyncExecutorAware, AsyncCallback<Some>() {
      private AsyncExecutor<Some> executor;
      public void setAsyncExecutor(AsyncExecutor<Some> executor) {
      this.executor = executor;
      }
      // 다른 메서드들 구현

      }

자바 5부터 새롭게 추가된 Concurrency API 중에서 Executor, 리턴이 가능한 Callable 및 Future에 대해서 살펴본다.

Executor를 이용한 쓰레드 관리

웹 서버와 같이 동시에 다수의 요청을 처리해야 하는 어플리케이션을 개발해야 할 경우 코드는 다음과 같은 형태를 띌 것이다.

while(true) {
    request = acceptRequest();
    Runnable requestHandler = new RequestHandler(request);
    new Thread(requestHandler).start();
}

위 코드가 논리적으로 문제점은 없지만, 다음과 같은 성능상의 문제점을 안고 있다.

  • 소규모의 많은 요청이 들어올 경우 쓰레드 생성 및 종료에 따른 오버헤드가 발생한다.
  • 생성되는 쓰레드 개수에 제한이 없기 때문에 OutOfMemoryError가 발생할 수 있다.
  • 많은 수의 쓰레드가 실행될 경우, 쓰레드 스케줄링에 따른 오버헤드가 발생한다.
이런 문제점 때문에 동시에 다수의 요청을 처리해야 하는 어플리케이션에서는 쓰레드 풀을 사용하여 동시에 실행될 수 있는 쓰레드의 개수를 제한하는 것이 일반적이다.

자바5부터 새롭게 추가된 Concurrency API는 작업을 실행하기 위한 Executor 인터페이스를 제공하고 있으며, 쓰레드 풀, 큐 등 다양한 Executor 구현체를 제공하고 있다. 따라서, 앞서 Thread를 직접 사용할 때의 문제점을 해소할 수 있게 되었다.

Executor를 이용한 쓰레드 관리

Executor와 관련된 주요 API는 다음과 같다.


위 클래스 다이어그램에서 각 인터페이스는 다음과 같은 기능을 정의한다.

  • Executor 인터페이스:
    제공된 작업(Runnable 구현체)을 실행하는 객체가 구현해야 할 인터페이스. 이 인터페이스는 작업을 제공하는 코드와 작업을 실행하는 메커니즘의 사이의 커플링을 제거해준다.
  • ExecutorService 인터페이스:
    Executor의 라이프사이클을 관리할 수 있는 기능을 정의하고 있다. Runnable 뿐만 아니라 Callable을 작업으로 사용할 수 있는 메소드가 추가로 제공된다.
  • ScheduledExecutorService:
    지정한 스케쥴에 따라 작업을 수행할 수 있는 기능이 추가되었다.
Executor 인터페이스

java.util.concurrent.Executor 인터페이스는 작업을 실행하는 클래스가 구현해야 할 인터페이스로서, 다음과 같이 정의되어 있다.

public interface Executor {
    void execute(Runnable command);
}

Executor 인터페이스의 구현체는 execute() 메소드로 전달받은 작업(Runnable 인스턴스)을 알맞게 실행하게 된다. 예를 들어, 쓰레드 풀을 구현한 Executor 구현체는 전달받은 작업을 큐에 넣은 뒤 가용한 쓰레드가 존재할 경우, 해당 쓰레드에 작업을 실행하도록 구현될 것이다.

아래 코드는 앞서 Thread를 사용했던 코드를 Executor를 사용하는 코드로 변환한 것이다.

Executor executor = …; // Executor 구현체 생성
while(true) {
    request = acceptRequest();
    Runnable requestHandler = new RequestHandler(request);
    executor.execute(requestHandler);
}

위 코드는 작업을 생성만 할 뿐 실제로 작업을 실행하지는 않는다. 단지, Executor.execute() 메소드에 생성한 작업을 전달할 뿐이다. 작업을 실제로 실행하는 책임은 Executor에 있으며, 이제 작업을 생성하는 코드에서는 작업이 어떻게 실행되는 지의 여부는 알 필요가 없다. 즉, Executor를 사용함으로써 작업을 생성하는 코드와 작업을 실행하는 메커니즘 사이의 커플링을 없앤 것이다.

ExecutorService 인터페이스

ExecutorService 인터페이스는 다음과 같이 두 가지 종류의 메소드가 추가되었다.

  • Executor의 라이프 사이클을 관리
  • Callable을 작업으로 사용하기 위한 메소드
먼저, 라이프 사이클과 관련된 메소드는 다음과 같다.

  • void shutdown():
    셧다운 한다. 이미 Executor에 제공된 작업은 실행되지만, 새로운 작업은 수용하지 않는다.
  • List<Runnable> shutdownNow():
    현재 실행중인 모든 작업을 중지시키고, 대기중인 작업을 멈추고, 현재 실행되기 위해 대기중인 작업 목록을 리턴한다.
  • boolean isShutdown():
    Executor가 셧다운 되었는 지의 여부를 확인한다.
  • boolean isTerminated():
    셧다운 실행 후 모든 작업이 종료되었는 지의 여부를 확인한다.
  • boolean awaitTermination(long timeout, TimeUnit unit):
    셧다운을 실행한 뒤, 지정한 시간 동안 모든 작업이 종료될 때 까지 대기한다. 지정한 시간 이내에서 실행중인 모든 작업이 종료되면 true를 리턴하고, 여전히 실행중인 작업이 남아 있다면 false를 리턴한다.
예를 들어, 웹 서버를 종료하게 되면 더 이상 클라이언트의 요청을 받아서는 안 되고, 기존에 처리중이던 요청은 지정한 시간내에서 처리해야 할 것이다. 이런 경우 ExecutorService를 사용하면 다음과 같이 종료 과정을 코딩할 수 있다.

executor.shutdown();
try {
    if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
        System.out.println("아직 처리중인 작업 존재");
        System.out.println("작업 강제 종료 실행");
        executor.shutdownNow();
        if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
            System.out.println("여전히 종료하지 않은 작업 존재");
        }
    }
} catch (InterruptedException e1) {
    executor.shutdownNow();
    Thread.currentThread().interrupt();
}
System.out.println("서버 셧다운 완료");

ExecutorService 인터페이스는 작업 수행과 관련해서 추가적으로 메소드를 제공하고 있으며, 이들 메소드는 다음과 같다.

  • <T> Future<T> submit(Callable<T> task)
    결과값을 리턴하는 작업을 추가한다.
  • Future<?> submit(Runnable task)
    결과값이 없는 작업을 추가한다.
  • <T> Future<T> submit(Runnable task, T result)
    새로운 작업을 추가한다. result는 작업이 성공적으로 수행될 때 사용될 리턴 값을 의미한다.
  • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    주어진 작업을 모두 실행한다. 각 실행 결과값을 구할 수 있는 Future의 List를 리턴한다.
  • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
    앞서 invokeAll()과 동일하다. 지정한 시간 동안 완료되지 못한 작업은 취소되는 차이점이 있다.
  • <T> T invokeAny(Collection<? extends Callable<T>> tasks)
    작업울 수행하고, 작업 결과 중 성공적으로 완료된 것의 결과를 리턴한다. 정상적으로 수행된 결과가 발생하거나 예외가 발생하는 경우 나머지 완료되지 않은 작업은 취소된다.
  • <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
    invokeAny()와 동일하다. 지정한 시간 동안만 대기한다는 차이점이 있다.
Callable은 Runnable과 비슷한데, 차이점이 있다면 결과 값을 리턴할 수 있다는 점이다. Future는 Callable의 결과값을 구하는 데 사용된다. Callable과 Future에 대해서는 뒤에서 자세히 살펴보도록 하자.

ScheduledThreadPoolExecutor 클래스

ScheduledThreadPoolExecutor 클래스는 ScheduledExecutorService 인터페이스을 구현한 클래스로서 스케줄링 기능을 제공한다. ScheduledThreadPoolExecutor 클래스는 또한 쓰레드 풀을 구현하고 있는 ThreadPoolExecutor 클래스를 상속받고 있기 때문에 쓰레드 풀 기능도 함께 제공한다.

ScheduledThreadPoolExecutor 클래스에 대한 자세한 내용은 아래 사이트를 참고하기 바란다.

  • http://java.sun.com/javase/6/docs/api/java/util/concurrent/ScheduledThreadPoolExecutor.html
Executors 유틸리티 클래스

java.util.concurrent.Executors 클래스는 자바 5가 기본적으로 제공하는 Executor 구현체를 구할 수 있는 메소드를 제공하는 유틸리티이다. 예를 들어, 쓰레드 풀을 구현한 Executor 구현체를 구하고 싶다면 다음과 같은 코드를 사용하면 된다.

Executor executor = Executors.newFixedThreadPool(THREADCOUNT);
while(true) {
    request = acceptRequest();
    Runnable requestHandler = new RequestHandler(request);
    executor.execute(requestHandler);
}

Executors 클래스는 newFixedThreadPool()과 더불어 다음과 같은 메소드를 이용하여 Executor 인스턴스를 구할 수 있도록 하고 있다.

  • ExecutorService newFixedThreadPool(int nThreads)
    최대 지정한 개수 만큼의 쓰레드를 가질 수 있는 쓰레드 풀을 생성한다. 실제 생성되는 객체는 ThreadPoolExecutor 객체이다.
  • ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
    지정한 개수만큼 쓰레드가 유지되는 스케줄 가능한 쓰레드 풀을 생성한다. 실제 생성되는 객체는 ScheduledThreadPoolExecutor 객체이다.
  • ExecutorService newSingleThreadExecutor()
    하나의 쓰레드만 사용하는 ExecutorService를 생성한다.
  • ScheduledExecutorService newSingleThreadScheduledExecutor()
    하나의 쓰레드만 사용하는 ScheduledExecutorService를 생성한다.
  • ExecutorService newCachedThreadPool()
    필요할 때 마다 쓰레드를 생성하는 쓰레드 풀을 생성한다. 이미 생성된 쓰레드의 경우 재사용된다. 실제 생성되는 객체는 ThreadPoolExecutor 객체이다.
Callable과 Future

Runnable 인터페이스는 다음과 같이 정의되어 있다.

public interface Runnable {
    public void run();
}

위 코드에서 볼 수 있듯이 Runnable.run() 메소드는 결과 값을 리턴하지 않기 때문에, run() 메소드의 실행 결과를 구하기 위해서는 공용 메모리나 파이프와 같은 것들을 사용해서 결과 값을 받아야만 했다. 이런 Runnable 인터페이스의 단점을 없애기 위해 추가된 것이 바로 Callable 인터페이스이다.

java.util.concurrent.Callable 인터페이스는 다음과 같이 정의되어 있다.

public Interface Callable<V> {
    V call() throws Exception
}

Callable 인터페이스의 call() 메소드는 결과 값을 리턴하도록 되어 있다. 또한, 자바 5부터 추가된 generic을 사용하여 어떤 타입이든 리턴 값으로 사용할 수 있도록 하였다. 예를 들어, 아래 코드는 Callable 인터페이스를 구현한 간단한 클래스를 보여주고 있다.

public class CallableImpl implements Callable<Integer> {
    public Integer call() throws Exception {
        // 작업 처리
        return result;
    }
}

ExecutorService.submit() 메소드를 사용하면 CallableImpl 클래스를 작업으로 사용할 수 있다. 아래 코드는 ExecutorService.submit() 메소드의 실행 예이다.

ExecutorService executor = Executors.newFixedThreadPool(THREADCOUNT);

Future<Integer> future = executor.submit(new CallableImpl());
Integer result = future.get();

ExecutorService.submit() 메소드는 전달받은 Callable 객체를 내부 메커니즘에 따라 지정한 때에 실행한다. 예를 들어, 위 경우 CallableImpl 객체는 큐에 저장되었다가 가용한 쓰레드가 생길 때 CallblaImpl.call() 메소드가 실행될 것이다.

Callable.call() 메소드가 ExecutorService.submit() 메소드에 전달될 때 곧 바로 실행되는 것이 아니기 때문에 리턴값을 바로 구할 수 없다. 이 리턴값은 미래의 어느 시점에 구할 수 있는데, 이렇게 미래에 실행되는 Callable의 수행 결과 값을 구할 때 사용되는 것이 Future이다. Future.get() 메소드는 Callable.call() 메소드의 실행이 완료될 때 까지 블록킹되며, Callable.call() 메소드의 실행이 완료되면 그 결과값을 리턴한다.

Future 인터페이스는 get() 메소드 외에도 다음과 같은 메소드를 제공하고 있다.

  • V get()
    Callable 등 작업의 실행이 완료될 때 까지 블록킹 되며, 완료되면 그 결과값을 리턴한다.
  • V get(long timeout, TimeUnit unit)
    지정한 시간 동안 작업의 실행 결과를 기다린다. 지정한 시간 내에 수행이 완료되면 그 결과값을 리턴한다. 대기 시간이 초과되면 TimeoutException을 발생시킨다.
  • boolean cancel(boolean mayInterruptIfRunning)
    작업을 취소한다.
  • boolean isCancelled()
    작업이 정상적으로 완료되기 이전에 취소되었을 경우 true를 리턴한다.
  • boolean isDone()
    작업이 완료되었다면 true를 리턴한다.
관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 2010.11.15 15:26  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  2. 2011.06.25 13:06  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  3. 오영감 2013.01.07 18:16 신고  댓글주소  수정/삭제  댓글쓰기

    좋은글 감사합니다.

  4. 2014.01.21 19:34 신고  댓글주소  수정/삭제  댓글쓰기

    쉽게 설명되어 있네요 감사합니다 ^^

  5. 2014.08.26 09:53 신고  댓글주소  수정/삭제  댓글쓰기

    정말 좋은글 감사합니다. 덕분에 자바 쓰레드 풀에 대한 기본정리를 하게되었네요

  6. Thread 정복 2015.01.31 16:25 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다~ 스레드 풀에 대해서 쉽게 설명이 되어 있어서 덕분에 빨리 이해되었습니다.

  7. 강신원 2015.08.10 23:01 신고  댓글주소  수정/삭제  댓글쓰기

    잘 정리해 놓으신 글 덕에 어려운 개념을 쉽게 이해하게 되었네요.
    고맙습니다. ^^

  8. 2015.09.18 10:08 신고  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 감사합니다.

  9. 리스 2016.07.11 09:55 신고  댓글주소  수정/삭제  댓글쓰기

    3일만에 소켓 서버 구성을 해야 했는데 덕분에 해결 되었습니다. 감사합니다.

  10. 이창현 2016.09.30 16:09 신고  댓글주소  수정/삭제  댓글쓰기

    정말 큰 도움이 되었습니다! 상당한 내공이 느껴지네요. 정리하시는 능력이 참 뛰어나신것 같아요 ^^ 감사합니다!

  11. fasdgoc 2016.11.14 20:15 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다...

    https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html

    Future는 어떤 비동기적인 계산에 대한 결과를 나타내는 클래스인데 get이 작업 결과를 얻을 때까지 블로킹한다고 합니다. get을 호출하는 쪽도 별도의 스레드인건가??