Asynchronized

2024. 3. 2. 11:52Spring Boot

 

우선 기본 설정으로

@EnableAsync를 붙여줘야 한다

import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

 

 

 

그후 원하는 메소드에 

@Async 어노테이션을 붙이면 쉽게 구현 할 수 있다.

import java.util.concurrent.Future;

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

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class async_service {
    
    static int num = 0;
    @Async
    void test_async() throws InterruptedException {
        Thread.sleep(100);
        log.info("test_async");
        num += 1;
    }

    void test_sync() throws InterruptedException {
        Thread.sleep(100);
        log.info("test_sync");
        num += 1;
    }

    void set_num() {
        num = 0;
    }

    int get_num() {
        return num;
    }
}

 

 

 

num에 1을 더하는 메소드를 동기화와 비동기화 상황일 때 비교해 보겠다

package com.example.demo.async;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.extern.slf4j.Slf4j;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;




@RestController
@RequestMapping("/async")
@Slf4j
public class async_controller {
    
    @Autowired
    async_service service;
    
    @GetMapping("/test1")
    public StringBuffer test1() throws InterruptedException {
        StringBuffer buf = new StringBuffer();
        
        service.set_num();
        long before = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            
            log.info("test1");
            service.test_async();
            buf.append(service.get_num());
            buf.append(' ');
        }
        long after = System.currentTimeMillis();
        buf.append("   time: ");
        buf.append(after - before);
        return buf;
    }

    @GetMapping("/test2")
    public StringBuffer test2() throws InterruptedException {
        StringBuffer buf = new StringBuffer();
        long before = System.currentTimeMillis();
        //service.set_num();
        for(int i = 0; i < 10; i++) {
            
            log.info("test2");
            service.test_sync();
            buf.append(service.get_num());
            buf.append(' ');
        }
        long after = System.currentTimeMillis();
        buf.append("   time: ");
        buf.append(after - before);
        return buf;
    }
    
    @GetMapping("/test3")
    public StringBuffer test3() {
        StringBuffer buf = new StringBuffer();
        buf.append(service.get_num());
        return buf;
    }

    @GetMapping("/test4")
    public StringBuffer test4() throws InterruptedException {
        return service.test_async2();
    }
    
}

 

 

 

 

 

 

@Async가 없는 동기화 메소드는

한 쓰레드에서 계속 계속 작업이 진행되고 있으므로

숫자값이 올라가고 문자열에 추가되는 과정이 순차적으로 진행되고

중간의 Thread.sleep()이 끝나기 전에 다음 과정이 진행되지 않기 때문에

문자열의 값이 늘 같고 결과 값은 10이고 시간이 오래 걸린다

 

 

 

 

 

하지만 비동기화 메소드를 부르는 경우에는

같은 쓰레드가 작업을 진행하는 것이 아니라

다른 쓰레드에 작업을 맡기고 기존 쓰레드는 작업을 계속 진행한다

그래서 메소드가 진행되서 값이 올라가는 걸 기다리지 않고
바로 값을 추가하고 메소드를 불리기 때문에 결과적으로는 값이 10이 되지만

작업 순서는 보장하지 못한다.

 

 

 

 

또한 AOP를 이용한 것이기 때문에 같은 class에 있는 @Async 메소드를 부르더라도 동기적으로 진행 된다.

 

 

 

 

 

 

 

 

이번에는 DB와 IO 작업을 하는 예제를 보이겠다

 

@Async
    public void create_data1(String data) {
        repo.save(new async_entity(data));
    }

    public void create_data2(String data) {
        repo.save(new async_entity(data));
    }

 

 

MySQL에 간단한 데이터를 처리하는 메소드를 동기화와 비동기화로 나누어서 만들고

 

@SpringBootTest
public class dbServiceTest {

    @Autowired
    async_db_service service;
    @Test
    void testCreate_data1() {
        for(int i = 0; i < 10000; i++) {
            service.create_data1(Integer.toString(i));
        }
    }

    @Test
    void testCreate_data2() {

        for(int i = 0; i < 10000; i++) {
            service.create_data2(Integer.toString(i));
        }
    }
}

 

각각 1만번 실행하는 테스트를 진행하였다

 

결과는 보다싶이 비동기화 쪽이 훨씬 빠르다

이는 동기화쪽은 한 작업이 끝나면 다른 작업이 시작되는데 반하여

비동기화는 한 작업이 실행중이더라도 다른 쓰레드에서 작업을 실행해서

여러작업이 한꺼번에 실행되기 때문이다.

 

 

즉 빠른 처리가 필요하고 중간에 다른 작업의 순서가 중요하지 않은 경우

비동기화 메소드를 이용하면 성능 향상을 꾀할 수 있다.

 

'Spring Boot' 카테고리의 다른 글

Spring WebFlux  (0) 2024.03.18
Spring WebClient  (0) 2024.03.10
Spring Boot Studying  (0) 2024.02.28
Authorization by Spring Security  (0) 2024.02.28
Jpa 순환 참조 N + 1 문제  (0) 2024.02.27