All Articles

Async in Java

The @EnableAsync annotation switches on Spring’s ability to run @Async methods in a background thread pool.

@Configuration
 @EnableAsync
 public class AppConfig {

 }

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

package com.example.asyncmethod;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@SpringBootApplication
@EnableAsync
public class AsyncMethodApplication {

  public static void main(String[] args) {
    // close the application context to shut down the custom ExecutorService
    SpringApplication.run(AsyncMethodApplication.class, args).close();
  }

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

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html

Spring will be searching for an associated thread pool definition: either a unique TaskExecutor  bean in the context, or an Executor bean named “taskExecutor” otherwise.

동시성 in Clean Code

Executor 프레임워크

  • 직접 생성한 스레드 풀 대신 고려해 볼 수 있음
  • 스레드 풀 관리, 크기 자동 조정, 재사용
  • Future(독립적인 연산 여럿을 실행한 후 모두가 끝나기를 기다릴 때), Runnable, Callable
Future<String> result = executorService.submit(makeExternalCll);
String partialResult = doSomeLocalProcessing();
return result.get() + partialResult // Future가 끝나기 기다림

Non bloking 방법

다중 스레드 환경에서 값을 안전하게 갱신하기 위해 동기화를 수행

  • synchronized
  • AtomicBoolean, AtomicInteger, AtomicReference

    private AtomicInteger value = new AtomicInteger(0);
    
    public void incrementValue() {
    	value.incrementAndGet();
    }
    
    public int getValue() {
    	return value.get();
    }

현대 프로세서는 CAS(compare and swap)을 제공하여 Atomic 연산이 항상 락을 거는 방법(synchronized)보다 빠르다

여러 스레드가 같은 값을 수정해 문제를 일으키는 상황이 잦지 않는 가정에서 효율적으로 감지해 갱신이 성공할 때까지 재차 시도한다. 현재 변수 값이 최종으로 알려진 값인지(메모리에 있는) 확인하고 변수 값을 갱신한다.

// non-blocking code 로 표현
int variableBeingSet;
void simulateNonBlockingSet(int newValue) {
	int currentValue;
	do {
		currentValue = variableBeingSet;
	} while (currentValue != compareAndSwap(currentValue, newValue));

int synchronized compareAndSwap(int currentValue, int newValue) {
	if (variableBeingSet == currentValue) {
		variableBeingSet = newValue;
	return currentValue;
	}
	return variableBeingSet;
}

Reference

https://en.wikipedia.org/wiki/Compare-and-swap

*In computer sciencecompare-and-swap(CAS) is an atomic instruction used in multithreading to achieve synchronization. It compares the contents of a memory location with a given value and, only if they are the same, modifies the contents of that memory location to a new given value.*

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

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableAsync.html