๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๊ฐœ๋ฐœ/Java

[Spring] background Thread ๋™์  ์ž‘์—… ์‹คํ–‰์‹œํ‚ค๊ธฐ ThreadPoolTaskExecutor :: ๋งˆ์ด์ž๋ชฝ

by ๐ŸŒปโ™š 2019. 4. 14.

Spring ๋™์  ์ž‘์—… ์‹คํ–‰ ThreadPoolTaskExecutor

์ƒ๊ฐ๋ณด๋‹ค ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ• ๋•Œ Runnable Thread๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์ง€ ์•Š๋‹ค. ํŠนํžˆ ์›น๊ฐœ๋ฐœ์ด๋‚˜ ์„œ๋ฒ„๊ฐœ๋ฐœ์„ ํ•˜๋‹ค๋ณด๋ฉด, ๊ฒŒ์ž„์ฒ˜๋Ÿผ ๋™์ ์ธ ๊ธฐ๋Šฅ๋ณด๋‹ค ์ •์ ์ธ ๊ธฐ๋Šฅ์ด ๋งŽ์ด ์š”๊ตฌ๋œ๋‹ค. ํ•„์ž๋Š” ํ”„๋กœ์ ํŠธ ์ง„ํ–‰์ค‘, API์™€ ์ง€์†์ ์ธ ์—ฐ๊ฒฐ์„ ํ•˜๋ฉด์„œ ๋‹ค๋ฅธ ์ž‘์—…๋“ค์„ ๋™์‹œ์— ํ• ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ฝ”๋“œ์ƒ์œผ๋กœ๋งŒ ๊ตฌํ˜„ํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์—์„œ Spring์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š” ThreadPoolTaskExecutor๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

๊ฐ„๋‹จํ•œ ๋ฐ๋ชจ์›นํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„๋•Œ ์ž‘์—…์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ง„ํ–‰์ด ๋˜๊ณ  ๋ฒ„ํŠผ์€ disable์‹œํ‚ค๋Š” ์ž‘์—…์„ ํ•ด๋ณผ๊ฒƒ์ด๋‹ค.

ํ”„๋กœ๊ทธ๋ ˆ์Šค๋ฐ”๋‚˜ ์ž‘์—…์˜ ์ง„ํ–‰์„ ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ์—ˆ์œผ๋‚˜... ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŒ…๋ชจ๋“ˆ์ด๋ผ ์ตœ๋Œ€ํ•œ ๊ฐ€๋ณ๊ฒŒ ์ƒˆ๋กœ๊ณ ์นจํ–ˆ์„๋•Œ ๋งˆ๋‹ค ํ™•์ธ์ด ๊ฐ€๋Š”ํ•˜๋„๋ก ๊ตฌํ˜„ํ•ด๋ณผ๋ ค๊ณ ํ•œ๋‹ค.

 

Package Tree

Background Thread ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด 4๊ฐœ์˜ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ• ๊ฒƒ์ด๋‹ค.

 

pom.xml

์ด๋ฒˆ๊ธ€์—์„œ๋Š” ํฌ๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๊ฐ€ ์—†์ง€๋งŒ, ์•ž์œผ๋กœ ajax ํ†ต์‹  ํ• ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ํŽธํ•˜๊ฒŒ ์ฃผ๊ณ  ๋ฐ›๊ธฐ ์œ„ํ•ด jackson databind ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. form ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ json ํ˜•ํƒœ๋กœ ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ›์•„์„œ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-core</artifactId>
		    <version>2.9.0.pr4</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		    <version>2.7.9.1</version>
		</dependency>     
	</dependencies>

 

servlet context

Spring ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ–ˆ์„๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ง€๋Š” context.xml ํŒŒ์ผ์— jsonView ๋งคํ•‘์ด ๋“ค์–ด์™”์„๋•Œ ์ž๋™์œผ๋กœ Object์™€ ๋งคํ•‘ํ•ด์ฃผ๋Š” ์„ค์ •์„ ํ•ด์ค˜์•ผํ•œ๋‹ค.

์ฃผ์˜ ์‚ฌํ•ญ์œผ๋กœ BeanNameViewResolver order๋ฅผ 0์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ค€๋‹ค.

์„ค์ •์„ ์•ˆํ•ด์ฃผ๋ฉด beanName์ด ์•„๋‹Œ view๋งคํ•‘์œผ๋กœ ๊ฐ„์ฃผํ•˜์—ฌ 404์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ• ๊ฒƒ์ด๋‹ค.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<context:component-scan base-package="test.*" />
	
	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	<beans:bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
		<beans:property name="order" value="0"></beans:property>
	</beans:bean>
	
	<beans:bean id="jsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
	
</beans:beans>

 

 

AsyncConfig.java

Task ์‹คํ–‰์„ ์œ„ํ•œ ์„ค์ •์„ ํ•ด์ฃผ๋Š” ํŒŒ์ผ์ด๋‹ค.

ํ•„์ž๋Š” ํ•ด๋‹น Task๊ฐ€ ์‹คํ–‰๋˜์—ˆ์„๋•Œ ๊ฐ™์€ Task๋ฅผ ์‹คํ–‰ํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ์ตœ๋Œ€ Thread ์ˆ˜๋ฅผ 1๋กœ ์ง€์ •ํ–ˆ๋‹ค.

๊ธฐ๋ณธ Thread ์ˆ˜๊ฐ€ ์ตœ๋Œ€ Thread ์ˆ˜๋ฅผ ๋„˜์•„๊ฐ€๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

package test.async.mongo.async;

import java.util.concurrent.Executor;

import javax.annotation.Resource;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/*
 * @Configuration : bean ๊ฐ์ฒด ๋“ฑ๋ก
 * @EnableAsync : ๋น„๋™๊ธฐ ํ”„๋กœ์„ธ์„œ ์‚ฌ์šฉ์„ 
 */
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer{
	
	//๊ธฐ๋ณธ Thread ์ˆ˜
	private static int TASK_CORE_POOL_SIZE = 1;
	//์ตœ๋Œ€ Thread ์ˆ˜
	private static int TASK_MAX_POOL_SIZE = 1;
	//QUEUE ์ˆ˜
	private static int TASK_QUEUE_CAPACITY = 0;
	//Thread Bean Name
	private final String EXECUTOR_BEAN_NAME = "executor1";
	
	@Resource(name="executor1")
	private ThreadPoolTaskExecutor executor1;
	
	@Bean(name="executor1")
	@Override
	public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(TASK_CORE_POOL_SIZE);
		executor.setMaxPoolSize(TASK_MAX_POOL_SIZE);
		executor.setQueueCapacity(TASK_QUEUE_CAPACITY);
		executor.setBeanName(EXECUTOR_BEAN_NAME);
		executor.setWaitForTasksToCompleteOnShutdown(false);
		executor.initialize();
		return executor;
	}
	
	/*
	 * Thread Process๋„์ค‘ ์—๋Ÿฌ ๋ฐœ์ƒ์‹œ
	 */
	@Override
	public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
		return new AsyncExceptionHandler();
	}
	
	/*
	 * task ์ƒ์„ฑ์ „์— pool์ด ์ฐผ๋Š”์ง€๋ฅผ ์ฒดํฌ
	 */
	public boolean checkSampleTaskExecute() {
		boolean result = true;
		
		System.out.println("ํ™œ์„ฑ Task ์ˆ˜ :::: " + executor1.getActiveCount());
		
		if(executor1.getActiveCount() >= (TASK_MAX_POOL_SIZE + TASK_QUEUE_CAPACITY)) {
			result = false;
		}
		return result;
	}
}

 

 

AsyncExceptionHandler.java

Task ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„๋•Œ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ๋‚ด์šฉ์ด๋‹ค.

package test.async.mongo.async;

import java.lang.reflect.Method;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;

public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler{

	@Override
		public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
		System.out.println("Thread Error Exception");
		System.out.println("exception Message :: " + throwable.getMessage());
		System.out.println("method name :: " + method.getName());
			for(Object param : obj) {
				System.out.println("param Val ::: " + param);
			}
		}
}

 

 

AsyncTaskService.java

ํ•ด๋‹น ํŒŒ์ผ์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰ํ•  ๋กœ์ง์„ ๊ตฌํ˜„ํ•œ๋‹ค.

๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ๋กœ 0~29๊นŒ์ง€ 1์ดˆ์— ํ•œ๋ฒˆ์”ฉ ๋กœ๊ทธ๊ฐ€ ์ฐํžˆ๋Š” ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.

package test.async.mongo.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service("asyncTaskService")
public class AsyncTaskService {
	
	@Async
	public void jobRunningInBackground(String temp) {
		System.out.println("Thread Start");
		for(int i=0; i < 30; i++) {
			System.out.println(temp + "  i ::::: " + i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("Thread End");
	}
}

 

 

HomeController.java

ableToRunThread ํŒŒ๋ผ๋ฏธํ„ฐ์— ํ˜„์žฌ ์“ฐ๋ ˆ๋“œ๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋Œ๊ณ  ์žˆ๋Š”์ง€์˜ ์—ฌ๋ถ€๋ฅผ ๋ณด๋‚ด๊ณ ,

AJAX ํ†ต์‹ ์„ ์œ„ํ•œ POST request์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

package test.async.mongo.web;

import javax.annotation.Resource;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import test.async.mongo.async.AsyncConfig;
import test.async.mongo.service.AsyncTaskService;

@Controller
public class HomeController {
	
	@Resource(name="asyncTaskService")
	private AsyncTaskService asyncTaskService;
	
	@Resource(name="asyncConfig")
	private AsyncConfig asyncConfig;
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public ModelAndView home() {
		ModelAndView mav = new ModelAndView("home");
		mav.addObject("ableToRunThread",asyncConfig.checkSampleTaskExecute());
		return mav;
	}
	
	
	@RequestMapping(value = "/testing", method = RequestMethod.POST)
	public ModelAndView testing() {
		ModelAndView mav = new ModelAndView("jsonView");
		try {
			//Task ์‹คํ–‰๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•
			if(asyncConfig.checkSampleTaskExecute()) {
				asyncTaskService.jobRunningInBackground("Jamong");
			}else {
				System.out.println("Thread ๊ฐœ์ˆ˜ ์ดˆ๊ณผ");
			}
		} catch (Exception e) {
			System.out.println("Thread Err :: " + e.getMessage());
		}
		return mav;
	}
}

 

 

home.jsp

ํด๋ผ์ด์–ธํŠธ ๋ถ€๋ถ„์˜ ์ฝ”๋“œ์ด๋‹ค.

์“ฐ๋ ˆ๋“œ๊ฐ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋Œ๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์—ฌ, ์ƒˆ๋กœ๊ณ ์นจ์„ ๋ˆŒ๋ €์„๋•Œ ๋ฒ„ํŠผ์˜ disable์—ฌ๋ถ€๋กœ ์ž‘์—…์˜ ์ง„ํ–‰ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค.

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
	<title>Home</title>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
	<script type="text/javascript">
	function testing(){
		$.ajax({
			url:"testing",
			type:"POST",
			cache:false,
			data:$("#form1").serialize(),
			async:false,
			success:function(data){
				$('#testingBtn').attr("disabled","disabled");
				$('#testingBtn').val('testing');
			},
			error:function(e){
				console.log("err : " + e);
			}
		});
	}
	</script>
</head>
<body>
<h1>
	Hello world!  
</h1>
<form name="form1" id="form1" method="post" onsubmit="return false;">
	<input type="text" name="txt1" id="txt1"/>
</form>
<c:choose>
	<c:when test="${ableToRunThread == false}">
		<input id="testingBtn" type="button" onclick="testing()" value="testing" disabled="disabled"/>
	</c:when>
	<c:otherwise>
		<input id="testingBtn" type="button" onclick="testing()" value="test Ready"/>
	</c:otherwise>
</c:choose>
</body>
</html>

๋Œ“๊ธ€