[스프링 핵심원리 - 고급편 ] ThreadLocal (쓰레드 로컬) 개념과 예제코드

JEONG SUJIN·2023년 2월 13일
0

스프링부트 기본

목록 보기
14/15

쓰레드 로컬이란

쓰레드 로컬은 해당 쓰레드만 접근할 수 있는 특별한 저장소.
쉽게 말해서 물건보관창고를 떠올리자.!
여러 사람이 같은 물건 보관창구를 사용하더라도 창구직원은 사용자를 인식해서 사용자별로 확실하게 물건을 구분한다.

사용자A, 사용자B 모두 창구 직원을 통해서 물건을 보관하고, 꺼내지만 창구 직원이 사용자에 따라 보관한 물건을 구분해주는 것.

쓰레드 로컬을 사용하면 각 쓰레드마다 별도의 내부 저장소를 제공한다. 따라서 같은 인스턴스의 쓰레드 로컬 필드에 접근해도 문제없다.

ThreadA가 userA를 쓰레드 로컬에 저장하면 쓰레드로컬은 thread-A전용 보관소에 데이터를 안전하게 보관한
다.
그리고 바로 ThreadB가 userB라는 값을 저장하면 쓰레드 로컬은 thread-B 전용보관소에 데이터를 안전하게 보관한다.

자바는 언어차원에서 쓰레드로컬을 지원하기 위한 java.lang.ThreadLocal 클래스를 제공한다.

ThreadLoal (쓰레드로컬) - 예제코드

ThreadLocalService.java

FieldService 만든 패키지안에 ThreadLocalService 파일생성

package study.advanced.trace.threadlocal.code;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ThreadLocalService {
	
	private ThreadLocal<String> nameStore = new ThreadLocal(); //<T> 타입지정, 난 String
	
	public String logic(String name) {
		log.info("저장 name={} -> nameStore={}", name, nameStore.get()); //조회할때 get()
		nameStore.set(name); //set사용하면 쓰레드로컬을 사용할 수 있다. 
		sleep(1000); //1초 쉬고
		log.info("조회 nameStore={}", nameStore.get()); //조회할때 get으로 꺼냄
		return nameStore.get(); // return(꺼낼때) get()으로 꺼내면된다. 
	}

	private void sleep(int millis) {
		try {
			Thread.sleep(millis);
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
	
}

ThreadLocal 사용법

  • 값 저장 : ThreadLocal.set()
  • 값 조회 : ThreadLocal.get()
  • 값 제거 : ThreadLocal.remove()

주의

해당 쓰레드가 쓰레드 로컬을 모두 사용하고 나면
ThreadLocal.remove()를 호출해서 쓰레드 로컬에 저장된 값을 제거해주어야 한다.

ThreadLocalServiceTest

sleep(2000); //2초동안 쉼, 동시성 문제 발생X 앞전과 동일하게 나온다.!

package study.advanced.trace.threadlocal;

import org.junit.jupiter.api.Test;

import lombok.extern.slf4j.Slf4j;
import study.advanced.trace.threadlocal.code.ThreadLocalService;

@Slf4j
public class ThreadLocalServiceTest {
	
	private ThreadLocalService ThreadLocalservice  = new ThreadLocalService();
	
	@Test
	void field() {
		log.info("main start");
		
		//쓰레드1 
		Runnable userA = () -> {
			ThreadLocalservice.logic("userA");
		};
		
		//쓰레드2
		Runnable userB = () -> {
			ThreadLocalservice.logic("userB");
		};
		
		
		Thread threadA = new Thread(userA);
		threadA.setName("thread-A"); // 쓰레드 이름설정 
		Thread threadB = new Thread(userB);
		threadB.setName("thread-B"); // 쓰레드 이름설정
		
		threadA.start(); // 쓰레드 A시작
		sleep(2000); //2초동안 쉼, 동시성 문제 발생X
		//sleep(100); //동시성 문제발생
		
		threadB.start(); //쓰레드 B시작
		sleep(2000); //메인 쓰레드 종료대기
		log.info("main exit");
		
		
	}

	private void sleep(int millis) {
		try {
			Thread.sleep(millis);
		}catch(InterruptedException e) {
			e.printStackTrace();
		}	
	}
	
}

ThreadLocalServiceTest.java

앞전 필드지정했을때 발생했던 동시성 문제 코드를 쓰레드로컬로 실행했을경우

sleep(100); //동시성 문제발생했던 코드

순서는 다르지만 userB값이 nameStore=userA가 아니라 null로 데이터 값이 잘 나오는걸 확인할 수 있다.

쓰레드 로컬 덕분에 쓰레드 마다 각각 별도의 데이터 저장소를 가지게 되었다.
결과적으로 동시성 문제도 해결되었다.

profile
기록하기

0개의 댓글