인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

emkemkemk님의 프로필 이미지
emkemkemk

작성한 질문수

재고시스템으로 알아보는 동시성이슈 해결방법

프록시 객체가 생성될 때 synchronized 없이 메서드가 생성되는 것이 맞을까요?

해결된 질문

작성

·

523

1

먼저 좋은 강의 감사드립니다.

프록시 객체에서 이해가 안 가는 부분이 있어 질문드립니다.

@Transactional을 사용하면 프록시 방식의 AOP로 동작하는 것은 이해하고 있습니다.

스프링 부트는 CGLIB 방식으로 프록시 객체를 생성하므로, StockService를 상속하는 StockServiceProxy가 만들어질 때 StockServiceProxy.decrease()에도 synchronized 키워드가 붙어있을 것이라고 생각했습니다.

그런데 강사님께서 TransactionStockService 를 예로 드실 때 synchronized 를 안 붙이신 걸 보니 프록시 객체가 생성될 때는 synchronized 가 안 붙는 건가? 라고 생각들었습니다.

Q. 프록시 객체가 생성될 때 synchronized 없이 메서드가 생성되는 것이 맞을까요?

읽어주셔서 감사합니다 :)

 

답변 1

1

최상용님의 프로필 이미지
최상용
지식공유자

HRO 님 안녕하세요.
프록시 객체가 생성될때는 synchronized 가 붙여진채로 생성이 됩니다.
TransactionStockService 는 StockService 의 Proxy 객체를 구현한것이 아니라 Transactional 어노테이션을 붙였을 때 저러한 방법으로 동작한다는 것을 쉽게 말씀드리기 위해 작성한 클래스입니다.

감사합니다.

emkemkemk님의 프로필 이미지
emkemkemk
질문자

강사님 빠른 답변 감사합니다.

조금 오래된 글이지만, synchronized 상속과 관련하여 다음 글을 구글링하였습니다.

The synchronized keyword is not considered to be part of a method's signature. So the synchronized modifier is not automatically inherited when subclasses override superclass methods, and methods in interfaces cannot be declared as synchronized. - https://gee.cs.oswego.edu/dl/cpj/mechanics.html

위 글에서는 결국 synchronized 는 메서드 시그니처가 아니므로, 오버라이딩 할 때 자동으로 상속되지 않는다고 얘기하고 있습니다.

강사님 말씀처럼 synchronized 가 붙여진 채로 생성된다면,

StockServiceProxy.decrease()는 다음과 같이 생겼을 걸로 예상됩니다.

@Override
public synchronized void decrease(Long id, Long quantity) {
    try{
        tx.start();
        stockService.decrease();
    } catch (Exception e) {
         // ...
    } finally {
        tx.commit();
    }
}

만약 synchronized 가 붙여진 채로 생성된다면, tx.commit() 이 호출되고 실제 DB에 커밋되기 전에 다른 스레드가 접근해서 커밋되기 이전의 quantity를 읽어서 갱신 손실 문제가 발생했다고 보면 될까요?

강의에서는 stockService.decrease() 만 synchronized의 적용을 받아, stockService.decrease()tx.commit() 사이에 다른 스레드가 quantity에 접근하여
갱신손실 문제가 일어난 것으로 설명해주셨습니다.

Q1. synchronized 는 원래 상속이 안 되는데 Proxy 객체 생성 시에만 synchronized 가 상속 되는 건가요?
Q2. Proxy 객체 생성 시에 synchronized 가 붙여진채로 메서드가 생성된다면, 강의에서 TransactionStockServicestockService.decrease()endTransaction 사이에 다른 스레드가 접근하는 것이 아니라 endTransaction 이 끝나고 DB 트랜잭션이 커밋되는 사이에 다른 스레드가 접근해서 갱신 손실 문제가 일어날 것 같습니다. 제가 이해한 것이 맞을까요?

긴 글 읽어주셔서 감사합니다!

 

최상용님의 프로필 이미지
최상용
지식공유자

HRO 님 안녕하세요.
저의 착각으로 전달을 잘못드린것 같습니다.
전달을 드리고자 했던 내용은 "synchronized 를 보장한다" 였습니다!
synchronized 는 구현되지 않는것이 맞으며 아래와 같은 매커니즘으로 동작합니다.

// Proxy class
class TransactionStockService {

    private StockService stockService;

    public void decrease(Long id, Long quantity) {
         try{
             tx.start();
             stockService.decrease();
         } catch (Exception e) {
             // ....
         } finally {
             tx.commit();
         }
    }
}

// Origin Class
class StockService {
    public synchronized void decrease(Long id, Long quantity) {
        // ....
    }
}

추가적인 질문이 있으시다면 답글 남겨주세요!
감사합니다 :)

emkemkemk님의 프로필 이미지
emkemkemk

작성한 질문수

질문하기