오봉이와 함께하는 개발 블로그

스프링 핵심 원리 - 고급편_쓰레드 로컬(쓰레드 로컬 소개, 예제 코드) 본문

BE/Spring

스프링 핵심 원리 - 고급편_쓰레드 로컬(쓰레드 로컬 소개, 예제 코드)

오봉봉이 2024. 3. 17. 23:38
728x90

ThreadLocal - 소개

쓰레드 로컬은 해당 쓰레드만 접근할 수 있는 특별한 저장소다.

일반적인 변수 필드

imageimage

  1. thread-AuserA라는 값을 저장하고
  2. thread-BuserB라는 값을 저장하면 userA는 사라진다.

쓰레드 로컬

간단하게 그림으로 표현하면 아래와 같다.

imageimageimage

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

ThreadLocal - 에제 코드

ThreadLocalService

package com.example.advanced.trace.threadlocal.code;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ThreadLocalService {

    private ThreadLocal<String> nameStore = new ThreadLocal<>();

    public String logic(String name) {
        log.info("저장 name={} -> nameStore={}", name, nameStore.get());
        nameStore.set(name);
        sleep(1000);
        log.info("조회 nameStore={}",nameStore.get());
        return nameStore.get();
    }

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

기존에 있던 FieldService 와 거의 같은 코드인데, nameStore 필드가 일반 String 타입에서 ThreadLocal 을 사용하도록 변경되었다.

ThreadLocal 사용법

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

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

ThreadLocalServiceTest

package com.example.advanced.trace.threadlocal;

import com.example.advanced.trace.threadlocal.code.ThreadLocalService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

@Slf4j
public class ThreadLocalServiceTest {
    private ThreadLocalService threadLocalService = new ThreadLocalService();

    @Test
    void field() {
        log.info("main start");
        Runnable userA = () -> {
            threadLocalService.logic("userA");
        };

        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(100);
        threadB.start(); //B실행
        sleep(2000); //메인 쓰레드 종료 대기 >> 마지막에 하지 않으면 그냥 field() 메서드가 종료되어 threadB 실행 결과가 나오지 않음
        log.info("main exit");
    }

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

실행 결과

[main] INFO com.example.advanced.trace.threadlocal.ThreadLocalServiceTest -- main start
[thread-A] INFO com.example.advanced.trace.threadlocal.code.ThreadLocalService -- 저장 name=userA -> nameStore=null
[thread-B] INFO com.example.advanced.trace.threadlocal.code.ThreadLocalService -- 저장 name=userB -> nameStore=null
[thread-A] INFO com.example.advanced.trace.threadlocal.code.ThreadLocalService -- 조회 nameStore=userA
[thread-B] INFO com.example.advanced.trace.threadlocal.code.ThreadLocalService -- 조회 nameStore=userB
[main] INFO com.example.advanced.trace.threadlocal.ThreadLocalServiceTest -- main exit

쓰레드 로컬을 사용하여 별도의 데이터 저장소를 가지게 되어 동시성 문제가 해결됐다.

 

출처: 김영한 지식공유자의 스프링 핵심 원리 고급편

728x90
Comments