본문 바로가기
Study

(Java) ThreadLocal 이란? 테스트 코드, 사용 예시

by Developer RyanKim 2020. 7. 25.

Java ThreadLocal

ThreadLocal 이란?

쓰레드 단위로 로컬 변수를 할당하는 기능을 제공하는 클래스이다. 자바 1.2 버전 부터 제공되고 있다.
간단히 예를 들면 메소드 안에서 선언된 로컬 변수는 메소드가 끝날 때 변수 사용이 종료되고, 리턴하거나 파라메터로 전달해주지 않으면

다른 메소드에서 사용할 수 없다. 하지만 ThreadLocal을 사용하면 쓰레드 범위로 변수가 할당되어 같은 쓰레드라면 다른 메소드에서도 변수 사용이 가능하다. 또한 다른 쓰레드에서 해당 값을 접근하거나, 변경하지 않는 것을 보장한다.

 

  • ThreadLocale.java
/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal&lt;Integer&gt; threadId =
 *         new ThreadLocal&lt;Integer&gt;() {
 *             &#64;Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
  ...
}

: 주석에 의하면 일반적으로 쓰레드 관련한 상태 값을 Class 의 private static field 로 사용할 수 있다고 한다.
( 예를 들어 user ID 혹은 Transaction ID )
 


- 테스트 코드

/**
 * Developer : ryan kim
 * Date : 2020-07-25
 */
public class ThreadLocalTest {

    @Test
    public void ThreadLocal_scope_test() throws InterruptedException {
        int numberOfThreads = 3;
        for(int i = 0; i < numberOfThreads; i++) {
            UserPropagation userPropagation = new UserPropagation();
            Thread thread = new Thread(userPropagation);
            thread.start();
        }

        Thread.sleep(3000);
    }
}


class UserPropagation implements Runnable {

    public static final ThreadLocal<Long> threadLocalUser = new ThreadLocal<>();
    public static final NormalUser normalUser = new NormalUser();

    @Override
    public void run() {
        UserPropagation.threadLocalUser.set(Thread.currentThread().getId());
        UserPropagation.normalUser.setId(Thread.currentThread().getId());

        System.out.println("start [Thread id : " + Thread.currentThread().getId() + "]");
        UserPrinter.printUser();
        UserPropagation.threadLocalUser.remove();
    }
}

class UserPrinter {

    static public void printUser() {
        Long thead = UserPropagation.threadLocalUser.get();
        Long normal = UserPropagation.normalUser.getId();
        System.out.println(
                "printUser [Thread id : " + Thread.currentThread().getId() + "] | threadLocalUser :"
                        + thead + "| normalUser : " + normal);
    }
}

class NormalUser {

    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

 

: 3개의 쓰레드를 생성하여 ThreadLocal 을 통해 할당한 Long 값(Thread id 값)과 일반 Class를 사용하여 할당한 값을 확인한다.

- 결과

start [Thread id : 14]
start [Thread id : 16]
start [Thread id : 15]
printUser [Thread id : 16] | threadLocalUser :16| normalUser : 16
printUser [Thread id : 15] | threadLocalUser :15| normalUser : 16
printUser [Thread id : 14] | threadLocalUser :14| normalUser : 16



: 쓰레드 14, 15, 16 이 실행되었고, 실행 순서는 일정하지 않다.
ThreadLocal 을 사용하여 할당한 변수는 쓰레드 범위 안에서 정확하게 할당 되어 print 되었다. (현재 쓰레드의 id 값)

하지만 사용하지 않은 변수는 예상하지 못한 값이 print 되었다. (현재 쓰레드의 id 값이 아님)
반복적으로 확인한 결과 threadLocal 변수는 항상 일정하게 자신의 thread id 값이었다.

하지만 사용하지 않은 변수는 16, 15 등 계속 값이 바뀌는 것이 확인되었다.


Java / Spring 코드에서 실제 어떻게 사용되고 있을까?


public class ThreadLocalSessionContext extends AbstractCurrentSessionContext {
    ...

	/**
	 * A ThreadLocal maintaining current sessions for the given execution thread.
	 * The actual ThreadLocal variable is a java.util.Map to account for
	 * the possibility for multiple SessionFactory instances being used during execution
	 * of the given thread.
	 */
	private static final ThreadLocal<Map<SessionFactory,Session>> CONTEXT_TL = ThreadLocal.withInitial( HashMap::new );
   

:  SessionFactoryImpl 에서 위 클래스 를 사용중이다.

 

public abstract class TransactionSynchronizationManager {

	private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");

	private static final ThreadLocal<String> currentTransactionName =
			new NamedThreadLocal<>("Current transaction name");

	private static final ThreadLocal<Boolean> currentTransactionReadOnly =
			new NamedThreadLocal<>("Current transaction read-only status");

	private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
			new NamedThreadLocal<>("Current transaction isolation level");

	private static final ThreadLocal<Boolean> actualTransactionActive =
			new NamedThreadLocal<>("Actual transaction active");
            
            ...
}

: JpaTransactionManager, HibernateTemplate, RedisTemplate 등에서 위 클래스를 사용중이다.

 

이처럼 쓰레드 단위로 Scope 관리가 필요한 인스턴스에 대해 ( Session, Trasaction .. ) 사용되고 있는 것을 볼 수 있다.

댓글