개발공부/SPRING

[Spring] 비동기 프로그래밍 & ThreadPoolExecutor 생성자 - 개념

키크니개발자 2023. 1. 2. 20:07

안녕하세요!

키크니 개발자 입니다. 🦒

 

비동기 프로그래밍에 대해서 강의를 보고 정리하고자 작성하였습니다. 😁

 

동기 / 비동기 개념

더보기

동기란?

  • 동기는 요청을 보낸 후 응답 (결과물)을 받아야지만 다음 동장이 이루어지는 방식을 말한다.
  • 모든 일은 순차적으로 실행되며 어떤 작업이 수행중이라면 다음 작업은 대기하게 된다.
    • 장점: 설계가 간단하고 직관적
    • 단점: 결과를 볼대 까지 아무것도 못하고 대기해야한다

비동기란?

  • 비동기는 동시에 일어나지 않는다 의미한다. 요청과 결과가 동시에 일어나지 않는 거라는 약속.
  • 요청한 그 자리에서 결과가 주어지지 않음
  • 노드 사이의 작업 처리 단위를 동시에 맞추지 않아도 된다.
    • 장점: 결과가 주어지는 데 시간이 걸리더라도 그동안 다른 작업이 가능해 자원의 효율적인 사용이 가능
    • 단점 : 설계가 동기보다 복잡함

아래의 사진을 보면서 동기와 비동기를 이해해보자!

  • 동기
    • 첫번째 손님이 아메리카노를 시켰다. 첫번 째 손님의 커피가 나올때 까지 2번째 손님은 기다려야한다.
    • 첫번째 손님이 요청한 아메리카노가 나온 뒤에 , 2번째 손님의 주문을 받을 수 있다.
    • 즉, 앞의 손님의 요청과 결과가 다 나올때까지 뒤의 손님은 계속 기다려야한다.
  • 비동기
    • 카운터에서 일단 첫번째 손님의 커피주문을 받는다. 첫번째 손님은 진동벨을 가지고 커피를 기다린다.
    • (첫번째 손님의 커피는 나오지 않았지만) 카운터에서 두번째 손님의 주문을 받는다.
    • 진동벨이 울리면, 첫번째 손님이 카운터로 와 커피를 받아간다.
    • 비동기 같은 경우, 일단 손님들의 요청을 계속 받으면서, 요청한 결과를 처리하는 한다.

비동기 언제 어떻게 사용하는가?

  • 실시간성 응답을 필요로 하지 않는 상황에서 사용한다.
    • ex) notification, email 전송(회원가입 후 축하합니다.), push 알림
  • main thread가 task를 처리하는 게 아니라 sub thread에게 비동기로 task를 위임하는 행위라고 말할 수 있다.
  • 그러면 여기서 main thread는 알아서 생성되고 자동적으로 처리할텐데 sub thread는 어떻게 생성하고 어떻게 관리를 해야할까?
    • spring에서 비동기 프로그래밍을 하기 위해선 ThreadPool을 정의할 필요가 있다.
    • 비동기는 Main Thread가 아닌 Sub Thread에서 작업이 진행한다.
    • java에서는 threadPool을 생성하여 async 작업을 처리한다.

ThreadPool 설정 시 다양한 옵션들

(https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html)

public ThreadPoolExecutor(int corePoolSize,
               		int maximumPoolSize,
                        long keepAliveTime,
                        TimeUnit unit,
                        BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
    	Executors.defaultThreadFactory(), defaulthandler);
}



// 사용 예시
ThreadPoolExecutor executorPool = 
	new ThreadPoolExecutor(5, 10, 3, TimeUnit, SECONDS, new ArrayBlockingQueue<Runnable>(50));

-> thread의 최솟값은 5개며 5개가 현재 생성되어있다. thread의 최대 사이즈는 10이므로 6에서 10번쨰 thread는 3초동안 일을 하지 않으면 자원을 반환한다. 
큐에는 50개까지의 request를 담을 수 있다.
  • CorePoolSize : 최소 몇개의 threadPool을 갖고 있을 것이냐?
    • ex : 이를 5개로 설정하면 사용하든 안하든 5개 thread는 무조건 resource를 정의하고 있다.
  • MaxPoolSize : 최대 thread를 몇 개까지 할당할것인가? (무한정 thread를 할당할 수 없다.)
  • WorkQueue : 어떤 요청이 들어오면 모든 thread들이 바로 진행할 수 없다. workQueue에 요청이 들어온 많은 작업들을 담아놓는다. 현재 작업하고있는 것이 끝나면 다음 작업할 task를 가져온다.
    • 💡 ex ) CorePoolSize가 3일때 request가 4개로 들어왔을 떄 여기서 바로 4번째 스레드를 생성하는 것이 아니라 WorkQueue에 담게 됩니다. WorkQueue 사이즈만큼 들어오는 요청들을 계속해서 담고, 그 WorkQueue의 사이즈만큼 요청이 쌓이면 그 다음 maxPoolSize만큼 thread를 생성하게 된다. (순서중요)
  • KeepAliveTime : 최소와 최대의 thread 개수를 왔다갔다 할텐데 CorePoolSize 보다 더 많은 thread를 할당하게 되면 언젠간 반환하게 되는데 반환하는 조건으로 KeepAliveTime을 설정한다. 내가 지정한 시간만큼 thread를 사용하지 않으면 반환한다.
  • unit : KeepAliveTime이 시간인지 분인지 초인지를 설정하는 단위

ThreadPool 생성시 주의해야 할 부분

  • CorePoolSize 값을 너무 크게 설정할 경우 side effect 고려해보기
    • 그 수 만큼은 자원을 점유하고 있기 때문에 너무 큰값을 설정하게 되면 이 threadPool은 잘 사용되지 않고, 혹 사용이 되더라도 그만큼의 Thread가 필요없는데도 불구하고 많은 스레드를 점유하고 있을 수 있기 때문에 적절하게 설정해야 한다.
    • 기본적으로 정의가 되어있는 ThreadPool이 있을 것이다. 그 값을 참고하여 설정을 하는 것이 일반적이다.
  • IllegalArgumentException, NullPointException 의 발생을 주의해야한다.
    (아래의 조건 중 하나라도 성립하면 exception이 발생한다.) 
      • corePoolSize < 0 : 메인 스레드가 스레드풀의 테스크를 위임하는데 스레드풀에 스레드가 없는 것이다.
      • keepAliveTime < 0 : 코어풀사이즈에서 스레드가 더 필요해서 새로운 스레드를 생성을 하려는데, 생성을 하자마자 바로 죽어야 된다는 것을 의미한다.
      • maximumPollSize ≤ 0 : 맥시멈풀사이즈가 0보다 작으면 코어풀사이즈가 0보다 작을 수 밖에 없다는 것을 의미한다. 이 스레드풀에는 단 1개의 스레드가 없기 때문에 exception 이 발생한다.
      • maximumPoolSize < corePoolSize : 미니멈이 맥시멈보다 큰 값을 가지는 것은 말이 안되기 때문에 익셉션 발생IllegalArgumentException - if one of ter following holds
    • NullPointerException - if workQueue is null
      • workQueue가 null일경우에는 워크큐.push, put 이런 명령어를 사용할텐데 정의되어있지 않는 큐에다가 값을 넣게 되면 NPE가 발생한다.

ThreadPool 정리

  • CorePoolSize
if (Thread 수 < CorePoolSize) {
		New Thread 생성
}

if (Thread 수 > CorePoolSize) {
		Queue에 요청 추가 
}
  • MaximumPoolSize
if (Queue Full && Thread 수 < MaxPoolSize) {
		New Thread 생성
}

if (Queue Full && Thread 수 > MaxPoolSize) {
		요청 거절
		(하지만 핸들링은 할 수 있다. 요청을 거절할 것인지(exception), 해당 요청을 무시하던지
		ThreadPoolExcutor를 생성할 때 여러가지 생성자가 있을텐데 exception을 핸들링을 설정할 수 있다.
}

 

 

 

References

10개 프로젝트로 완성하는 백엔드 웹개발(Java/Spring) : https://fastcampus.co.kr/dev_online_befinal

https://velog.io/@alicia-mkkim/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0%EB%9E%80

반응형