@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는 하나의 프로세스 안에서만 보장되기 때문에 서버가 여러개일 경우 효과를 볼 수 없으며, 추가적인 작업이 필요하다.
성능 저하가 발생할 수 있기 때문에, 꼭 필요한 경우에 잘 사용해야 한다.