최근에 업무하면서

TaskExecutor과 TaskScheduler를 모두 쓸 일이 있었다

 

그래서 개념에 대해서 깊게 보고싶어서

토비의 스프링을 펼쳐보았다^^7777

역시 이거만한게 없다..

 

정리해보쟛...ㅁ7ㅁ8

 

 


1. TaskExecutor

task실행기.. 그렇담 task는 뭘까?

 

Task : 독립적으로 실행한 가능한 작업

 

스프링은 이러한 task들을 다양하게 실행하도록 추상화하여 TaskExecutor라는 인터페이스를 제공한다.

 

package org.springframework.core.task;

import java.util.concurrent.Executor;

@FunctionalInterface
public interface TaskExecutor extends Executor {
    void execute(Runnable var1);
}

 

java5의 Executor 인터페이스를 상속한다.

 

Runnable타입의 태스크를 받아 실행하는데,

다음 인터페이스는 독립적인 스레드에 의해 실행 되도록 의도된 오브젝트를 만들 때 주로 사용된다.

package java.lang;

@FunctionalInterface
public interface Runnable {
    void run();
}

 

Spring의 TaskExecutor는 java.lang.concurrent 패키지 의 Executor와 똑같은 메소드를 가지고 있다. 

 

package java.util.concurrent;

public interface Executor {
    void execute(Runnable var1);
}

 

그럼에도 스프링에서 다시 만든이유는 다음과 같다.

 

1. 다른 기술의 태스크 실행기에 대한 어댑터를 제공 ( Quartz , CommonJ WorkManager...)

2. 스프링에 최적화된 방식의 태스크 실행기 확장 

 

반드시 비동기 독립적 스레드에서 실행될 필요는 없지만 ,

대부분 비동기로 쓴닷 ㅎ_ㅎ

 

 

2. TaskExecutor 구현체

 

2.1 ThreadPoolExecutor

corePoolSize, maxPoolSize , queueCapacity 속성을 설정할 수 있다.

지정된 크기의 스레드 풀을 이용하며 , 작업 요청은 큐를 통해 관리된다.

가장 대표적인 태스크 실행기다.

 

 

   @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }

 

2.2 SimpleThreadPoolTaskExecutor

Quartz의 SimpleThreadPool을 이용해 만들어진 태스크 실행기이다.

 

2.3 WorkManagerTaskExecutor

CommonJ WorkManager의 태스크 실행기에 대한 어댑터이다.

 

 

2.4 SyncTaskExecutor

별도의 스레드에서 수행되는게 아니라 호출한 스레드 상에서 호출

 

 


3. TaskScheduler 

다음 인터페이스는 주어진 태스크를 조건에 따라 실행하거나 반복하는 작업을 수행

태스크의 실행조건은

1) 특정시간

2) 일정한 간격을 두고 반복

3) Trigger인터페이스를 구현해서 유연한 조건 

 - > cron 서버의 실행시간 설정 포맷을 활용한 cronTrigger가 가장 대표적인 구현 클래스이다. 

 

package org.springframework.scheduling;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
import org.springframework.lang.Nullable;

public interface TaskScheduler {
    @Nullable
    ScheduledFuture<?> schedule(Runnable var1, Trigger var2);

    default ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
        return this.schedule(task, Date.from(startTime));
    }

    ScheduledFuture<?> schedule(Runnable var1, Date var2);

    default ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) {
        return this.scheduleAtFixedRate(task, Date.from(startTime), period.toMillis());
    }

    ScheduledFuture<?> scheduleAtFixedRate(Runnable var1, Date var2, long var3);

    default ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
        return this.scheduleAtFixedRate(task, period.toMillis());
    }

    ScheduledFuture<?> scheduleAtFixedRate(Runnable var1, long var2);

    default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay) {
        return this.scheduleWithFixedDelay(task, Date.from(startTime), delay.toMillis());
    }

    ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1, Date var2, long var3);

    default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
        return this.scheduleWithFixedDelay(task, delay.toMillis());
    }

    ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1, long var2);
}

 

 

4. TaskScheduler 구현체

 

4.1 ThreadPoolTaskScheduler

JDK의 ShcdeuledThreadPoolExecutor 스케쥴러에 대한 어댑터이다.

 

 

    @Bean
    public TaskScheduler scheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("Scheduler-Thread-");
        scheduler.initialize();
        return scheduler;
    }

 

 

4.2 TimerManagerTaskScheduler

 

참고) 유연한 조건을 이용!할때 쓰는 Trigger 인터페이스

 

package org.springframework.scheduling;

import java.util.Date;
import org.springframework.lang.Nullable;

public interface Trigger {
    @Nullable
    Date nextExecutionTime(TriggerContext var1);
}

 

그 Trigger 인터페이스를 구현한 CronTrigger  CronExpression을 지원한다.

package org.springframework.scheduling.support;

import java.util.Date;
import java.util.TimeZone;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;

public class CronTrigger implements Trigger {
    private final CronSequenceGenerator sequenceGenerator;

    public CronTrigger(String expression) {
        this.sequenceGenerator = new CronSequenceGenerator(expression);
    }

    public CronTrigger(String expression, TimeZone timeZone) {
        this.sequenceGenerator = new CronSequenceGenerator(expression, timeZone);
    }

    public String getExpression() {
        return this.sequenceGenerator.getExpression();
    }

    public Date nextExecutionTime(TriggerContext triggerContext) {
        Date date = triggerContext.lastCompletionTime();
        if (date != null) {
            Date scheduled = triggerContext.lastScheduledExecutionTime();
            if (scheduled != null && date.before(scheduled)) {
                date = scheduled;
            }
        } else {
            date = new Date();
        }

        return this.sequenceGenerator.next(date);
    }

    public boolean equals(Object other) {
        return this == other || other instanceof CronTrigger && this.sequenceGenerator.equals(((CronTrigger)other).sequenceGenerator);
    }

    public int hashCode() {
        return this.sequenceGenerator.hashCode();
    }

    public String toString() {
        return this.sequenceGenerator.toString();
    }
}

 

 


5. Annotaion 활용 ( @Scheduled , @Async )

 

5.1 @Scheduled

태스크 역할을 맡은 메소드에 직접 스케줄 정보를 어노테이션을 통해 부여해 수케줄이 적용되게 해준다.

@Scheduled 부여되는 메소드는 파라미터를 가질 수 없으며 반드시 void형 리턴 타입이어야한다.

 

-  fixedDelay : 이전 작업이 끝난 시점부터 일정시간이 지난후에 동작하도록 설정 ,

 이전 작업이 끝난후로 부터 정해진 시간이 지난후 다음작업이 시작된다.

@Schduled (fixedDelay=60000)
public void testFixedDelay(){...}

 

- fixedRate : 밀리초로 설정된 일정한 시간간격으로 메소드 실행 

@Schduled (fixedRate=60000)
public void testFixedRate(){...}

 

- cron : Cron expression 으로 메소드 실행

@Schduled (cron = "0 0 12 1 * ?")
public void testCron(){...}

 

 

5.2 @Async 

TaskExecutor를 코드로 사용하지 않고도 비동기 실행이 가능하게 해주는 어노테이션이다.

( 그런데 설정이 필요하면.. Bean으로 만들어주는게 좋다.. threadpool size 등등..)

 

리턴타입은  void 또는 Future 타입이어야한다.

메소드는 다른 코드에 의해 직접 호출 되므로 파라미터를 가질 수 있다. 

 

더자세한 내용은 이전에 포스팅한 이글을 참조한다.

https://hyeonyeee.tistory.com/55

 

Spring에서 Async 처리 (@Async )

Spring에서 Async처리를 해보겠다..! 블로그에 정리되어 있는게 많았는데 그중에 어떤 방법을 택할까 고민을 했다. 가장 간단한 방법으로 구현하였다. @Async annotaion을 붙여주는 방법이다. 1. @EnableAsyn

hyeonyeee.tistory.com

 

 

 


6. 결론

 

정리하다보니 더 자세히 이해가 되었다.

그리고 이전에 개발한 코드가 더 잘 와닿게 되었다 후후

 

그래서 한마디로 정리하자고 하면

 

TaskExecutor는 task를 주로 비동기적으로 처리할때 쓰고

TaskScheduler는 스케줄링할때 쓴다.

 

이 두개 모두 @Async , @Scheduled 라는 어노테이션으로 대체가 가능한데,

자세한 설정이 필요하면

bean으로 만들어 주는것이좋다~ (@Configuraion...)

 

그럼 정리 끝 - 

Spring에서 Async처리를 해보겠다..!

 

블로그에 정리되어 있는게 많았는데 그중에 어떤 방법을 택할까 고민을 했다.

 

 

 

가장 간단한 방법으로 구현하였다.

 


 

@Async 

annotaion을 붙여주는 방법이다.

 

1. @EnableAsync로 async를 쓰겠다고 스프링에게 알린다.

2. 비동기로 수행되었으면 하는 method위에 @Async annotaion 을 붙인다.

3. method return 값은 void나 CompletableFuture<?>로 해주어야한다.

 

 

 

@EnableAsync은 Application을 run하는 데에다 붙여주거나,@Configuration annotaion 있는데 붙여주면 된다.

 

그리고 thread를 관리해주어야 하기 때문에 bean을 설정해준다.

 

@Bean
  public Executor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("GithubLookup-");
    executor.initialize();
    return executor;
  }

 

spring boot 2.0이상이면, application.yml파일에 작성해도 auto configuration 으로 Excutor를 등록해준다.

 

 

이렇게해서,, 테스트를 해봤었는데 잘안되었다

 

이유는???

 

@Async를 활용하기 위해서는 몇가지 제약 조건이 있기 때문이다.

 

 

1.public method만 @Async가 가능하다. private는 불가능 
2.self-invocation(자가 호출)해서는 안된다. -> 같은 클래스 내부의 메서드를 호출하는 것은 안된다.

 

 

나같은 경우는 2번의 경우였다.

한 service에서 계속 function call을 내부적으로 하는데 

그중에 한, method만 비지니스적으로 async하게 call하고 싶었다.

 

 

그래서 찾은 방법은???

async해야 하는 method를 다른 service로 빼서 , 

기존 service에 AsyncService를 주입하고 (@Autowired)

async 해야하는 method에 @Async를 붙여준다음,

service에서 call 하면 되는 것이다 (간단간단 '-' 역시.. 알면 간단하다)

 

 

기존 service

@Service

public class 기존service{

@Autowired

AsyncService asyncService;

 

public void 해당method(){

      asyncService.asyncMethod();

   }

}

 

 

AsyncService

@Service

public class Asyncservice{

 

@Async

    ComletableFuture<OutVO>asyncMethod(){

      OutVO output = new OutVO();

 

       return ComletableFuture.completedFuture(output);

   }

}

 

 

 

그런데 도대체 왜그럴까?? ( 블로그 글 참조)

 

결론부터 말하면 AOP가 적용되어 Spring context에 등록되어 있는 빈 객체의 메서드가 호출되었을 때 스프링이 끼어들 수 있고 @Async가 적용되어 있다면 스프링이 메서드를 가로채서 다른 스레드(풀)에서 실행시켜주는 메커니즘이라는 것이다.

 

public이어야 가로챈 스프링의 다른 클래스에서 호출이 가능하고,

self-invocation이 불가능 했던 이유도 spring context에 등록된 빈의 메서드 호출 이어야

프록시를 적용 받을 수 있기에 내부 메서드 호출은 프록세 영향을 받지 않기 때문이다.

 

 

 

 

 


참고 사이트 :

https://spring.io/guides/gs/async-method/

 

Spring

Level up your Java code and explore what Spring can do for you.

spring.io

 

https://jeong-pro.tistory.com/m/187

 

How does @Async work? @Async를 지금까지 잘 못 쓰고 있었습니다(@Async 사용할 때 주의해야 할 것, 사용법)

@Async in Spring boot 스프링 부트에서 개발자에게 비동기 처리를 손쉽게 할 수 있도록 다양한 방법을 제공하고 있다. 대세는 Reactive stack, CompletableFuture를 쓰겠으나 역시 가장 쉬운 방법으로는 @Async..

jeong-pro.tistory.com

 

 

+ Recent posts