본문 바로가기

FrontEnd/Javascript

Java와 JS의 동기적 비동기 구현방식

참고: https://velog.io/@pllap/Java%EC%97%90%EC%84%9C%EC%9D%98-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D

간단히 요약하면 비동기 구현을 한다고 말로는 표현하지만 두 언어 모두 그 안에는 동기함수인 콜벡함수가 공통적으로 쓰인다. 다만 문법의 이름이 다르다는 것이다. JS는 Promise, async, await라는 문법이 있고 Java에는 Thread, ExecutorService, CompletionHandler등등이 있는 것이다.


비동기 처리한다고 하여 순수히 비동기로 구성하는 것이 아니다. 글에서 나와 있다시피 비동기로 작동하는 코드 사이에 동기 함수를 끼워넣어서 코드의 흐름을 조정하는 것이다. 
즉, 비동기 처리한다는 것은 동기 함수를 끼워넣는 다는 것이다! 

비동기 프로그래밍이라는 키워드로 구글링해 보면, 가장 많이 뜨는 것이 '자바스크립트에서 비동기 처리를 어떻게 하는가' 이다. 왜 자바스크립트와 관련된 비동기 처리만 이렇게 많이 뜨는 걸까?
자바스크립트는 특성상 웹 페이지의 코드를 작성하거나 웹 서버를 작성할 때 사용되는데, 이런 환경에서 제공되는 API는 속도를 최우선으로 하기 위해 비동기 함수로 작성되었기 때문이다.

아래 코드를 보면 알수 있듯이 비동기적 흐름에 동기 함수이자 콜벡함수인 completed(혹은 failed)을 사용하여 비동기 처리 방식을 구현하였다(비동기 처리 방식은 그 안에 반드시 동기함수를 넣는다).

CompletionHandler

import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CallbackExample1 {

    private static ExecutorService executorService;
    // CompletionHandler를 구현한다.
    private static final CompletionHandler<String, Void> completionHandler = new CompletionHandler<>() {
        // 작업 1이 성공적으로 종료된 경우 불리는 콜백 (작업 2)
        @Override
        public void completed(String result, Void attachment) {
            log("작업 2 시작 (작업 1의 결과: " + result + ")");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log("작업 2 종료");
        }

        // 작업 1이 실패했을 경우 불리는 콜백
        @Override
        public void failed(Throwable exc, Void attachment) {
            log("작업 1 실패: " + exc.toString());
        }
    };

    public static void main(String[] args) {

        executorService = Executors.newCachedThreadPool();

        // 작업 1
        executorService.submit(() -> {
            log("작업 1 시작");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log("작업 1 종료");

            String result = "Alice";
            if (result.equals("Alice")) { // 작업 성공
                completionHandler.completed(result, null);
            } else { // 작업 실패
                completionHandler.failed(new IllegalStateException(), null);
            }
        });

        // 별개로 돌아가는 작업 3
        log("작업 3 시작");
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log("작업 3 종료");
    }

    private static void log(String content) {
        System.out.println(Thread.currentThread().getName() + "> " + content);
    }
}

위 코드의 처리 순서에 있어서 왜 작업3이 작업 1보다 먼저 실행되었는지 의문을 가질수 있다.ExecutorService 객체의 특성 때문에 그렇다. 즉,실행시작이 비교적 적게 걸리는 작업3부터 먼저 실행하게끔 구성된 것이 ExecutorService객체이다. 

실행 결과

main> 작업 3 시작
pool-1-thread-1> 작업 1 시작
pool-1-thread-1> 작업 1 종료
pool-1-thread-1> 작업 2 시작 (작업 1의 결과: Alice)
main> 작업 3 종료
pool-1-thread-1> 작업 2 종료


이와 같이 비동기 처리 방식에는 콜벡함수가 거진 필수적으로 들어가게 된다(콜벡함수라고 명시적으로 알리지 않더라도 그개념은 콜백이다. 비동기 처리방식은 동기함수인 콜백함수를 통해 구현된다).