@Entity
@Getter
@NoArgsConstructor(access = PROTECTED)
@AllArgsConstructor
public class Member extends Auditable {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
private Long totalRecord;
member 엔티티의 필드로 회원의 누적 운동 시간(totalRecord)
를 가지고 있었고, totalRecord에 대한 요청이 왔을 때 Dto에서도 totalRecord 필드를 바로 반환하고 있었다.
Spring Bean은 싱글톤(Singleton)으로 관리되고, 이는 애플리케이션에 딱
하나만 존재
한다는 뜻이다.
주의해야 할 점은 객체 필드가 상태값을 가지도록, 즉 stateful하도록 설계해서는 안된다.
여기서 발생한 문제 또한 statful한 인스턴스의 필드를 여러 thread에서 동시에 접근하면서 발생
한 문제이다.
thread는 stack 영역을 제외한 프로세스의 메모리 영역을 공유
하기 때문이다.
따라서 각 thread는 별도의 stack 영역
을 갖고 있기 때문에, 지역 변수
또는 매개변수
에 대해서는 동시성 문제가 발생하지 않는다.
반면, 인스턴스의 필드는 프로세스의 Data 영역
에 저장되기 때문에 동시성 이슈로부터 자유롭지 못한 것이다.
동시성 문제는 인스턴스의 필드
또는 static 필드
에 접근할 때 발생한다.
이러한 동시성 문제는 지역 변수에서는 발생하지 않는다
.
특히 스프링 빈 처럼 Singleton 객체의 필드를 변경하며 사용할 때 이러한 동시성 문제를 조심해야 한다.
여기서도 인스턴스 필드에 여러 thread에서 동시에 접근하게 될 경우 문제가 발생할 수 있다.
ThreadLocal
사용Synchronized
사용- 인스턴스의 필드가
상태값을 갖지 않도록
설계 수정
현재 나의 상황에서는 3번 방법이 그리 큰 작업이 아니었기 때문에 이 방법으로 금방 해결할 수 있었다. 하지만 만일 비즈니스상 상태값을 가져야만 하는 상황이거나, 수정하는 데에 큰 비용이 드는 상황이라면 다른 방법이 필요하다.
그래서 호기심에 여러가지 방법을 찾아보았다.
특정 thread만 접근할 수 있는 특별한 저장소로써, 해당 thread를 식별하여 thread마다 저장소를 구분할 수 있다.
잘못 사용하면 메모리 누수를 일으켜 큰 장애를 야기할 수 있다
예를 들어, thread를 사용 후 threadpool에 반환할 때 remove()
를 통해 반드시 초기화해야 한다. 톰캣의 경우 threadpool에 반환 후, 다른 곳에서 재사용
되기 때문에, 이전의 데이터를 초기화해주지 않으면 다른 곳에서 그대로 사용하게 된다.
Java의 Synchronized 를 사용하여 동기화를 수행할 수 있다.
Synchronized는 하나의 프로세스 안에서만 보장되기 때문에 서버가 여러개일 경우 효과를 볼 수 없으며, 추가적인 작업이 필요하다.
성능 저하가 발생할 수 있기 때문에, 꼭 필요한 경우에 잘 사용해야 한다.