- springboot
- PYTHON
- spring security 6
- 스프링부트
- SSL
- sql
- string
- 자바
- 데이터베이스
- 스프링
- Django
- 프로그래머스
- jpa
- @transactional
- Docker
- nginx
- spring boot
- join
- ORM
- spring
- 문자열
- mysql
- hibernate
- AWS
- DI
- select
- static
- spring mvc
- java
- 1차원 배열
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
개발하는 자몽
동시성 문제와 쓰레드 로컬 본문
동시성 문제와 쓰레드 로컬이 무엇인지 간단하게 정리해 보자.
동시성 문제
아래 코드에는 store라는 필드 변수와 해당 변수에 매개 변수를 저장하는 bizLogic() 메서드가 있다. 해당 필드 변수를 저장하고 조회하는 bizLogic 메서드에 여러 쓰레드가 동시에 접근하면 store의 값은 어떻게 될까?
@Slf4j
class FieldBiz {
private String store; //필드 변수
public String bizLogic(String s) {
log.info("저장 전 : store={} s={}", store, s);
store = s;
sleep(1000);
log.info("저장 후 : store={} s={}", store, s);
return store;
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
아래 코드를 통해 두 개의 쓰레드가 동시에 필드 변수에 접근해 보자.
class FieldBizTest {
private FieldBiz fieldBiz = new FieldBiz();
@Test
void field() {
Runnable rbA = () -> {
fieldBiz.bizLogic("rbA");
}
Runnable rbB = () -> {
fieldBiz.bizLogic("rbB");
}
Thread threadA = new Thread(rbA);
threadA.setName("thread-A");
Thread threadB = new Thread(rbB);
threadA.setName("thread-B");
threadA.start();
sleep(100); //동시성 문제 발생 O
threadB.start();
sleep(3000); //메인 쓰레드 종료 대기
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
실행 결과
저장 전 s=rbA -> store=null
저장 전 s=rbB -> store=rbA
저장 후 store=rbB
저장 후 store=rbB
동시에 접근하지 않고 한 쓰레드의 실행이 완전히 종료되고 그다음에 실행했다면 store의 값은 차례대로 rbA, rbB가 되었을 것이다.
위와 같은 문제를 동시성 문제라고 한다. 이런 동시성 문제는 지역 변수에서는 발생하지 않는다. 지역 변수는 쓰레드마다 각각 다른 메모리 영역이 할당되기 때문이다(지역 변수는 stack 영역에 저장됨을 기억하자). 주로 같은 인스턴스의 필드(주로 싱글톤에서 자주 발생), 또는 static 같은 공유 필드에 접근할 때 발생한다. 지금 상황은 지역 변수가 아닌 필드 변수인 store에 동시에 접근하여 발생했다. 단순히 조회만 했다면 괜찮지만, 이렇게 값을 변경하면 동시성 문제가 발생한다.
이러한 동시성 문제를 해결하기 위해서 자바는 언어 차원에서 ThreadLocal(java.lang.ThreadLocal)이라는 클래스를 제공한다.
쓰레드 로컬
쓰레드 로컬을 이용하여 위 동시성 문제를 해결해 보자.
@Slf4j
class ThreadLocalBiz {
private ThreadLocal<String> store = new ThreadLocal<>();
public String bizLogic(String s) {
log.info("저장 전 s={} -> store={}", s, store.get());
nameStore.set(s); //저장
sleep(1000);
log.info("저장 후 store={}", store.get()); //조회
return nameStore.get();
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
class ThreadLocalBizTest {
private ThreadLocalBiz threadLocalBiz = new ThreadLocalBiz();
@Test
void field() {
Runnable rbA = () -> {
threadLocalBiz.bizLogic("rbA");
}
Runnable rbB = () -> {
threadLocalBiz.bizLogic("rbB");
}
Thread threadA = new Thread(rbA);
threadA.setName("thread-A");
Thread threadB = new Thread(rbB);
threadA.setName("thread-B");
threadA.start();
sleep(100); //동시성 문제 발생 O
threadB.start();
sleep(3000); //메인 쓰레드 종료 대기
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
ThreadLocal을 사용한 위 코드를 실행하면 아래와 같은 결과를 확인할 수 있다.
저장 전 s=rbA -> store=null
저장 후 store=rbA
저장 전 s=rbB -> store=null
저장 후 store=rbB
쓰레드 로컬은 해당 스레드만 접근할 수 있는 장소를 제공하기 때문에 thread-A와 thread-B가 접근하는 장소는 다르다. 따라서 필드 변수인 store에 동시에 접근해도 서로 다른 장소에 접근하기 때문에 전과 같은 동시성 문제는 발생하지 않는다.
!주의!
쓰레드 로컬을 사용할 때 주의할 점이 있다. 해당 쓰레드가 쓰레드 로컬의 사용이 끝났다면 Thread.remove()를 호출하여 쓰레드 로컬에 저장된 값을 제거해줘야 한다. 그렇지 않으면 메모리 누수 문제가 발생할 수 있다.
SUMMARY
- 싱글톤이나 공용 필드에 여러 쓰레드가 동시에 접근하면 동시성 문제가 발생할 수 있다.
- 위 문제를 ThreadLocal을 사용하여 해결할 수 있다.
ThreadLocal.get(); //조회
ThreadLocal.set(); //저장
Threadlocal.remove(); //제거
- 쓰레드 로컬을 모두 사용했다면, remove()를 호출하여 쓰레드 로컬에 저장된 값을 제거해야 한다.
'Java & Kotlin' 카테고리의 다른 글
[Tomcat Error] java.lang.IllegalArgumentException: The main resource set specified [...\tomcat\tomcat.8080/webapps] is not valid (0) | 2023.04.12 |
---|---|
리플렉션(Reflection) (0) | 2023.03.28 |
Bean Validation - HTTP 메시지 컨버터 (0) | 2023.03.13 |
Bean Validation - 에러 코드 (0) | 2023.03.10 |
Class.isAssignableFrom (0) | 2023.03.08 |