원문 : Sometimes it's best not to share

Threading lightly 라는 시리즈 중에 3부에 해당하는 내용으로 직역하자면 '가볍게 쓰레딩하기' 정도가 되겠습니다. 멀티쓰레드 환경에서 Thread-safety를 보장하는 일이 꽤 까다로운 편인데, 이러한 글들로 도움을 주는 멋쟁이들이 있으니 참으로 편하게 공부할 수 있는 세상이 아닌가 싶습니다.

제목이 저런 이유는 간단합니다. 멀티쓰레드 환경에서 Thread-safety 문제가 발생하는 원인이 바로 여러 쓰레드에서 공유 자원에 접근하기 때문인데, 바로 그 점을 ThreadLocal을 사용하여 각 쓰레드에서 사용할 데이터를 분리 시키면 Thread-safety 문제를 걱정할 필요없다는 점에서 이런 재밌는 제목을 지은 것 같습니다.

참조 & 요약 & 번역

예를 들어, JDBC connection여러 쓰레드에서 공유하여 사용하는 것은 매우 세밀하게 보면 불가능 하다. 그러나 각각의 쓰레드마다 Connection 객체를 가지게 되면 그런 일을 걱정하지 않아도 된다. 물론 Thread API에서도 다른 객체와 연관하는 기능을 지원하지만(전 잘 모르겠지만;; 그렇다는 군요.), ThreadLocal을 사용하는 것이 더 간단하다.

What is a thread-local variable?

쓰레드 로컬 변수는 각각의 쓰레드가 사용하는 값의 분리된 복사본을 효율적으로 제공한다. 각각의 쓰레드는 오직 자기 자신과 연관된 값만 참조할 수 있으며 다른 쓰레드들이 어떤 것을 사용하는지 그들의 복사본을 수정했는지는 알 수 없다.

다른 언어에서는 storage-class modifier(static 또는 volatile) 를 사용하여 쓰레드 로컬 변수를 지원 하지만 자바는 그렇치 않고 쓰레드 로컬의 구현체인 ThreadLocal 클래스를 제공한다.(자바에도 static 이랑 volatile이 있는데.. 이 글을 작성할 시점에는 아직 volatile이 없었나 봅니다. 근데 지금도 volatile은 잘 안쓰이는 것으로 알고 있는데 아마 이러한 의도로 만들어진 키워드 같네요.)

자바에서는 키워드가 아니라 클래스기 때문에 아무래도 다른 언어에 비해 사용하기가 다소 번잡하다. 쓰레드 로컬을 사용하려면 ThreadLocal 객체를 만들어 사용하면 된다.

Using ThreadLocal to implement a per-thread Singleton

쓰레드 로컬 변수는 보통 상태를 유지하는 싱글톤(stateful Singleton)을 사용하기 위해서 또는 공유 객체의 thread-safe를 위해서 사용한다. 각각 전체 객체를 Threadlocal에 넣거나 객체의 쓰레드 관련 상태를 ThreadLocal에 넣어서 캡슐화(encapsulation)하는 방법을 사용한다.

예를 들어, JDBC Connection 객체를 사용하는 프로그램에서 이 객체를 모든 메소드의 인자로 넘겨주는 것은 상당히 불편한 일이다. 그래서 singleton 객체를 사용하는데 이 때 멀티쓰레드 환경이라면 thread-safe를 보장하지 못한다. 따라서 다음과 같은 코드를 사용하여 ThreadLocal로 쓰레드 당 싱글톤으로 Connection 객체를 유지할 수 있다.

public class ConnectionDispenser {
    private static class ThreadLocalConnection extends ThreadLocal {
        public Object initialValue() {
            return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
        }
    }

    private static ThreadLocalConnection conn = new ThreadLocalConnection();

    public static Connection getConnection() {
        return (Connection) conn.get();
    }
}

물론 풀링(pooling)을 사용하여 공유 접근을 관리 할 수 있지만 even pooling has some potential drawbacks from a scalability perspective. Because pool implementations must synchronize to maintain the integrity of the pool data structures, if all threads are using the same pool, program performance may suffer due to contention in a system with many threads accessing the pool frequently.(많은 쓰레드들이 풀에 자주 접근하면 지연 문제가 발생할 수 있다는 단점이 있다.)

Using ThreadLocal to simplify debug logging

멀티 쓰레드 환경에서의 각 쓰레드 당 로깅 메시지를 남기고 가져오는 일을 하는 애플리케이션을 작성할 때도 유용하다.

ThreadLocal is also useful in servlet-based applications or any multithreaded server application in which the unit of work is an entire request, because then a single thread will be used during the entire course of handling the request. You can use ThreadLocal variables to store any sort of per-request context information using the per-thread-singleton technique described earlier.

위의 영어 문단이 오늘 ThreadLocal을 공부하게 된 계기 입니다. Acegi를 공부하다 보니 ThreadLocal이 자주 등장합니다. 따라서 이 부분에 대한 이해가 필요했는데 원한 만큼 건진 것 같습니다. 하지만 조금만 더 건져보죠. 재밌으니까요.

ThreadLocal's less thread-safe cousin, InheritableThreadLocal

ThreadLoacl의 친척 뻘인 InheritableThreadLocal이 있다. 이 녀석은 부모 쓰레드에서 자식 쓰레드를 만들 때 InheritableThreadLocal 객체에 부모에 해당하는 데이터가 있다면 그것을 자식에게도 물려 주는 특징이 있습니다. 이때 Thread-safe를 유지하려면 immutabl(한번 만들면 변하지 않는)한 데이터만을 InheritableThreadLocal 넣어 두어야 합니다.

ThreadLocal performance

1.2에 처음 만들어 지고 1.3에 많은 개선(원문 참조)이 있었지만 성능이 많이 떨어졌고, 1.4에서 조금 나아졌다.

ThreadLocal should be faster than other techniques such as pooling. Because it is simpler and often less error-prone than those other techniques, it will eventually be discovered as an effective way to prevent undesired interactions between threads.

5.0와 6.0에서는 어떻게 됐을까요?? 모르겠습니다. 아시는 분은 댓글을...;;

The benefits of ThreadLocal

1. It is often the
easiest way to render a stateful class thread-safe
2. encapsulate
non-thread-safe classes so that they can safely be used in
multithreaded environments
3. allows us
to bypass the complexity of determining when to synchronize in order
to achieve thread-safety
4. it improves scalability because it
doesn't require any synchronization
5. 기타 등등등..