프로젝트/coin-trading

6. 비동기 처리(AtomicBoolean, Future<?>)

DJ.Kang 2025. 2. 25. 16:25
    private final ConcurrentHashMap<String, Future<?>> userTrades;
    private final ExecutorService executorService;
    private final Map<String, AtomicBoolean> userRunningStatus;
    private final BackDataRepository backDataRepository;
    private final UpbitService upbitService;

- 프로그램 실행

    public void startTrading(AuthUser authUser) {
        String userId = authUser.getUserId();

        // 이미 실행 중인 거래 프로그램이 있는지 확인
        if (userTrades.containsKey(userId)) throw new CustomException(ErrorCode.TRADING_ALREADY_GENERATE);

        // 각 사용자의 running 상태 가져오기, 없으면 false로 초기화
        AtomicBoolean userRunning = userRunningStatus.computeIfAbsent(userId, k -> new AtomicBoolean(false));

        // 새로운 작업을 실행하고 Future로 저장
        Future<?> future = executorService.submit(() -> {
            try {
                // 거래 프로그램 실행 전에 running 상태를 true로 변경
                userRunning.set(true);
                startProgram(authUser);
            } catch (Exception e) {
                log.info("프로그램 실행 중 오류 발생: {}", e.getMessage());
            }
        });

        // 사용자별로 실행 상태를 userTrades에 저장
        userTrades.put(userId, future);
    }
  • 유저 정보 가져오기
  • 실행여부 확인하기
  • AtomicBoolean을 사용하여 가져온 유저에 대한 실행상태 추적하기
    ※ 일반적인 boolean 변수는 멀티스레드 환경에서 경쟁 조건(Race Condition)이 발생할 수 있다.
  • executorService.submit() 를 사용하여 비동기 실행 및 Future<?>객체로 쓰레드관리
     Future<?>는 비동기 작업(쓰레드)의 실행 결과를 나타내는 객체

- 프로그램 종료

    public void stopTrading(AuthUser authUser) {
        String userId = authUser.getUserId();

        // 실행중인 프로그램 가져오기
        Future<?> future = userTrades.remove(userId);
        if (future == null) throw new CustomException(ErrorCode.TRADING_NOT_FOUND);

        // 각 사용자의 running 상태 가져오기
        AtomicBoolean userRunning = userRunningStatus.get(userId);

        // 사용자별로 running을 false로 설정하여 while문 종료
        userRunning.set(false);

        // 작업 종료
        try {
            future.cancel(true);
            log.info("{}의 거래 프로그램이 정상적으로 종료되었습니다.", userId);
        } catch (Exception e) {
            log.info("프로그램 종료 중 오류 발생: {}", e.getMessage());
        }
    }
  • userTrades에서 user의 future꺼낸 후 삭제하기
  • 동작상태 가져오기
  • running를 false로 set하여 안전하게 종료
  • future.cancel하여 프로그램 종료

- 테스트 코드

@Test
    void stopTrading() throws Exception {
        // given
        AuthUser authUser1 = new AuthUser("user1", "nick1", "secret1", "access1");
        AuthUser authUser2 = new AuthUser("user2", "nick2", "secret2", "access2");

        // when & then
        // 프로그램 2개 동작
        executorService.execute(() -> tradingService.startTrading(authUser1));
        Thread.sleep(1000);
        assertThat(userTrades).hasSize(1);
        executorService.execute(() -> tradingService.startTrading(authUser2));
        Thread.sleep(1000);
        assertThat(userTrades).hasSize(2);
        assertThat(userRunningStatus.get("user1").get()).isTrue();
        assertThat(userRunningStatus.get("user2").get()).isTrue();

        // 프로그램1 종료(실행 1 종료 1)
        tradingService.stopTrading(authUser1);
        assertThat(userTrades).hasSize(1);
        assertThat(userRunningStatus.get("user1").get()).isFalse();
        assertThat(userRunningStatus.get("user2").get()).isTrue();

        // 프로그램2 종료(실행 0 종료 2)
        tradingService.stopTrading(authUser2);
        assertThat(userTrades).hasSize(0);
        assertThat(userRunningStatus.get("user1").get()).isFalse();
        assertThat(userRunningStatus.get("user2").get()).isFalse();
    }