쓰레드 로컬은 해당 쓰레드만 접근할 수 있는 특별한 장소를 말한다. 쉽게 말해, 여러 사람이 같은 물건 보관창구를 사용하더라도 창구 지원은 사용자를 인식해서 사용자별로 확실하게 물건을 구분해준다.
쓰레드 로컬이 없을때(동시성 문제 발생)
쓰레드 로컬이 있을때( 각 쓰레드마다 별도의 내부 저장소를 제공)
자바는 언어차원에서 쓰레드 로컬을 지원하기 위한 java.lang.ThreadLocal 클래스를 제공한다
2.ThreadLocal 코드
기존에 있던 FieldService 와 거의 같은 코드인데,
nameStore 필드가 일반String 타입에서 ThreadLocal 을 사용하도록 변경되었다.
ThreadLocal 테스트 코드
쓰레드 로컬 덕분에 쓰레드 마다 각각 별도의 데이터 저장소를 가지게 되었다. 결과적으로 동시성 문제도 해결되었다
ThreadLocal 적용 전 코드
ThreadLocal 적용 후 코드(수정 코드 redline)
ThreadLocal 동기화 적용 코드(수정 코드 redline)
Tip
interface로 구현을 하면 보다 쉽게 수정이 가능하다.!!
기존의 LogTrace가 인터페이스다. repository, service,controller 클라이언트 코드를 다 변경하지 않고 의존관계 주입을 통해 모두 수정가능!!! OCP(개방 폐쇄원칙) 지키면서 다 바꿀수 있다.
각 쓰레드에 맞게 호출이 잘 나왔다.!!
3.결론
ThreadLocal 사용법
값 저장: ThreadLocal.set(xxx)
값 조회: ThreadLocal.get()
값 제거: ThreadLocal.remove()
ThreadLocal 사용시 주의사항을 확인하고 사용하자!!!
주의 해당 쓰레드가 쓰레드 로컬을 모두 사용하고 나면 ThreadLocal.remove() 를 호출해서 쓰레드 로컬에 저장된 값을 제거해주어야 한다.
ThreadLocal은 쓰레드별로 독립적인 값을 유지하고 관리하는 자바의 클래스이다. 각 쓰레드는 자신만의 ThreadLocal 인스턴스를 가지며, 해당 인스턴스에 값을 저장하거나 조회할 수 있게된다. 이렇게 다수의 쓰레드가 동시에 ThreadLocal 을 사용해도 서로의 값을 침범하지 않는다.
하지만, ThreadLocal 은쓰레드별로 독립적인 값을 저장하므로 메모리 누수의 위험이 발생할 수 있다. 예를 들어 쓰레드에서 ThreadLocal 에 값을 저장한 뒤에 해당 쓰레드가 종료되지 않고 재사용 되는 경우, 이전에 저장한 값이 그대로 남아있게 된다. 이러한 경우 ThreadLocal 에 저장된 값이 더이상 필요하지 않더라도 메모리를 계속 차지하게 되므로, 메모리 누수가 발생할 수 있다.
ThreadLocal 사용시 메모리 누수 방지 1) try-finally 블록을 사용하여 ThreadLocal.remove() 를 호출하거나, 2) ThreadLocal 의 초기값 설정에 initialValue() 메서드를 사용하여 자동으로 제거되도록 설정하는 방법
좀 더 쉽게 그림으로 설명하자면
쓰레드 로컬의 값을 사용 후 제거하지 않고 그냥 두면 WAS(톰캣)처럼 쓰레드 풀을 사용하는 경우에 심각한 문제가 발생할 수 있다
사용자A 저장 요청
1. 사용자A가 저장 HTTP를 요청했다.
2. WAS는 쓰레드 풀에서 쓰레드를 하나 조회한다.
3. 쓰레드 thread-A 가 할당되었다.
4. thread-A 가 동작하면서 사용자A 의 데이터를 쓰레드 로컬에 저장한다.
5. 쓰레드 로컬의 thread-A 전용 보관소에 사용자A 데이터를 보관한다
사용자A 저장 요청 종료 (REMOVE 미 적용)
1. 사용자A의 HTTP 응답이 끝난다.
2. WAS는 사용이 끝난 thread-A 를 쓰레드 풀에 반환한다. 쓰레드를 생성하는 비용은 비싸기 때문에 쓰레드를 제거하지 않고, 보통 쓰레드 풀을 통해서 쓰레드를 재사용한다.보통 쓰레드 풀에 여러개를 만들어 놓고 기존 만들어진걸 쓰면 빠르게 재사용할 수 있 .
3. thread-A 는 쓰레드풀에 아직 살아있다. 따라서 쓰레드 로컬의 thread-A 전용 보관소에 사용자A 데이터도 함께 살아있게 된다.제거를 하지 않았기 때문에 계속 살아있다.
사용자B 조회 요청
1. 사용자B가 조회를 위한 새로운 HTTP 요청을 한다.
2. WAS는 쓰레드 풀에서 쓰레드를 하나 조회한다.
3. 쓰레드 thread-A 가 할당되었다. (물론 다른 쓰레드가 할당될 수 도 있다.)
4. 이번에는 조회하는 요청이다. thread-A 는 쓰레드 로컬에서 데이터를 조회한다.
5. 쓰레드 로컬은 thread-A 전용 보관소에 있는 사용자A 값을 반환한다.
6. 결과적으로 사용자A 값이 반환된다.
7. 사용자B는 사용자A의 정보를 조회하게 된다
결과적으로 사용자B는 사용자A의 데이터를 확인하게 되는 심각한 문제가 발생하게 된다. 이런 문제를 예방하려면 사용자A의 요청이 끝날 때 쓰레드 로컬의 값을 ThreadLocal.remove() 를 통해서 꼭 제거해야 한다. 쓰레드 로컬을 사용할 때는 이 부분을 꼭! 기억하자.