[Java] Async vs Sync, 함수 비동기 호출하기

Jinbro·2022년 7월 31일
1

Java

목록 보기
3/7

Async 비동기 처리

  • 요청(함수 호출) 에 대한 응답 (반환값) 이 오는 것을 기다리지 않고 다음 로직 바로 수행
  • ex) 샐러드 가게에서 한 명이 주문받고 n명이 샐러드를 만든다. client는 이전 주문이 완료되기 전에 본인이 주문한 샐러드를 받을 수 있다.
  • Javascript 가 동작하는 web browser는 비동기적으로 일을 처리한다.
    => Promise, async-await 를 통해 동기 처리 구현 가능

Sync 동기 처리

  • 요청(함수 호출) 에 대한 응답 (반환값) 이 오기 전까지 기다림 => 다음 로직 수행 X
  • ex) 1인 운영 샐러드 가게에서 혼자 주문받고 샐러드를 만든다. 2개 이상의 주문이 들어올 경우, clinet는 이전 주문이 모두 완료되기 전까지 기다려야한다.
  • Java 는 동기적으로 일을 한다.
    => Java 비동기 처리 구현은 어떻게 할 수 있을까?

Java 동기/비동기 처리 구현하기

문제인식

  • WAS (java) > n개의 PDF 파일 전송 Service > 1개의 PDF 파일 전송 API n번 호출 > 동기 처리 > 성능 이슈
  • Service 종료 시점 = 모든 API 소요 시간 합

해결방법

  • 비동기 호출 > PDF 파일 전송 API 다건 동시 호출
  • Service 종료 시점 = 가장 오래 걸리는 API 종료 시점

Java 비동기 구현 클래스

  • Future (Java 1.5) : 미래 시점에 응답 결과를 얻을 수 있음. 호출자 Thread가 응답 결과를 기다리는 동안 다른 작업을 수행할 수 있음.
    get() : 스레드 호출 응답 결과 가져올 수 있음. 응답 완료 실패 시, blocking -> 예외처리 필요
  • ExecutorService : Thread의 실행을 관리하고 제어 (Executor 클래스 상속)
  1. 결과를 반환하는 Thread
  2. Thread 집합을 실행하고 종료 상태를 결정하는 메소드 제공
메소드명목적parameter반환값정의 주체
execute()Thread 실행 결과 상관없이 병렬 실행RunnableXExecutorService
submit()Thread 실행 결과에 관심이 있을 경우 (후처리)Runnable, CallableFuture (비동기 연산의 결과)Executor
  • CompletableFuture (Java 1.8) : Future 한계점 보완. get() 이전에 콜백 정의 가능.
    내부적으로 존재하는 ForkJoinPool 쓰레드풀에 의해 작업이 실행
    join : Future.get() 과 동일한 기능

Sample Source : 샐러드 가게 주문, 대기시간에 따른 총 소요시간 출력

1. 샐러드 dto 생성

  • Salad.java
@Getter
@Setter
public class Salad {
	private String name; 	// 이름
	private int waitTime; 	// 대기시간 (msec)
    
    public Salad(String name, int waitTime) {
		super();
		this.name = name;
		this.waitTime = waitTime;
	}
}

2. 샐러드 마켓

  • SaladMarket.java
public class SaladMarket {

	/**
     * Salad 주문 목록
     */
	private final List<Salad> saladList = Arrays.asList(
			new Salad("기본", 1000),
			new Salad("닭가슴살", 5000),
			new Salad("리코타치즈", 2000),
			new Salad("오리훈제", 4000));
    /**
	 * 샐러드 만들기
	 */
	public Salad makeSalad(Salad salad) {
		Salad resultSalad = salad;

		// 샐러드 만드는 시간동안 sleep
		delay(salad.getWaitTime());

		String printStr = String.format("%s 샐러드 만드는 시간 %d msec 소요 되었습니다.", salad.getName(), salad.getWaitTime());
		System.out.println(printStr);
		
		return resultSalad;
	}
    /**
	 * 샐러드 완성 대기시간 동안 sleep
	 */
	public int delay(int waitTime) {
		try {
			Thread.sleep(waitTime);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return 0;
	}
    ...
}

3. 동기 호출

  • SaladMarket.makeSaladSequencial
	/**
	 * 샐러드 만들기 - 동기 순차 호출
	 */
	public void makeSaladSequencial() {
		saladList.forEach(salad -> {
			makeSalad(salad);			
		});
	}
  • console log
[동기 호출] 샐러드 가게 인력난으로 혼자 주문받고, 샐러드를 만들고 있습니다. 샐러드 만들기 시작합니다!
기본 샐러드 만드는 시간 1000 msec 소요 되었습니다.
닭가슴살 샐러드 만드는 시간 5000 msec 소요 되었습니다.
리코타치즈 샐러드 만드는 시간 2000 msec 소요 되었습니다.
오리훈제 샐러드 만드는 시간 4000 msec 소요 되었습니다.
[동기 호출] 완료 시간:  12022 msecs

4. 비동기 호출 - Java 1.5 (Future, ExecutorService, Callable)

  • SaladMarket.makeSaladAsyncFuture
	/**
	 * 샐러드 만들기 - 비동기 호출 (Future)
	 */
	public void makeSaladAsyncFuture() {
		// Thread 결과 리턴 받기 위한 Future List
		final List<Future<Salad>> futureList = new ArrayList<>();

		for (final Salad salad : saladList) {
			try {
				// Thread 풀 생성
				ExecutorService executor = Executors.newFixedThreadPool(1);

				// Thread 실행 작업 정의
				Callable<Salad> callable = new Callable<>() {

					@Override
					public Salad call() {
						return makeSalad(salad);
					}
				};

				Future<Salad> future = executor.submit(callable);
				futureList.add(future);
				executor.shutdown();

			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		// 샐러드 만들기 완료
		if (futureList.size() > 0) {
			for (Future<Salad> future : futureList) {
				try {
					Salad resultSalad = future.get();
                    // 샐러드 만들기 후처리 로직 실행
				} catch (InterruptedException e) {
					e.printStackTrace();
				} catch (ExecutionException e) {
					e.printStackTrace();
				}
			}
		}
	}
  • console log
[Future 비동기 호출] 샐러드 가게 인력이 충분합니다. 1명이 주문받고, n명이 주문들어오는대로 샐러드를 만들고 있습니다. 샐러드 만들기 시작합니다!
기본 샐러드 만드는 시간 1000 msec 소요 되었습니다.
리코타치즈 샐러드 만드는 시간 2000 msec 소요 되었습니다.
오리훈제 샐러드 만드는 시간 4000 msec 소요 되었습니다.
닭가슴살 샐러드 만드는 시간 5000 msec 소요 되었습니다.
[Future 비동기 호출] 완료 시간:  5010 msecs

5. 비동기 호출 - Java 1.8 (CompletableFuture)

  • SaladMarket.makeSaladAsyncCompletableFuture
	/**
	 * 샐러드 만들기 - 비동기 호출 (CompletableFuture)
	 */
	public List<Salad> makeSaladAsyncCompletableFuture() {
		List<CompletableFuture<Salad>> completableFutureList = saladList.stream()
				.map(salad -> CompletableFuture.supplyAsync(() -> makeSalad(salad)))
				.collect(Collectors.toList());
		return completableFutureList.stream()
				.map(CompletableFuture::join)
				.collect(Collectors.toList());
	}
  • console log
[CompletableFuture 비동기 호출] 샐러드 가게 인력이 충분합니다. 1명이 주문받고, n명이 주문들어오는대로 샐러드를 만들고 있습니다. 샐러드 만들기 시작합니다!
기본 샐러드 만드는 시간 1000 msec 소요 되었습니다.
리코타치즈 샐러드 만드는 시간 2000 msec 소요 되었습니다.
오리훈제 샐러드 만드는 시간 4000 msec 소요 되었습니다.
닭가슴살 샐러드 만드는 시간 5000 msec 소요 되었습니다.
[CompletableFuture 비동기 호출] 완료 시간:  5025 msecs

참고

profile
자기 개발 기록 저장소

0개의 댓글