하나의 문서에서 ThreadLocal의 원리 및 사용 시나리오 이해

머리말

일 이던 면접이던ThreadLocal 오늘은 수다를 떨자 ThreadLocal~

  1. ThreadLocal이란 무엇이며 ThreadLocal을 사용하는 이유
  2. ThreadLocal 사용 사례
  3. ThreadLocal의 원리
  4. 스레드 ID를 ThreadLocalMap의 키로 직접 사용하지 않는 이유
  5. 메모리 누수가 발생하는 이유는 무엇입니까? 약한 참조 때문입니까?
  6. Key가 약한 참조로 설계된 이유는 무엇입니까? 강한 참조를 할 수 없습니까?
  7. InheritableThreadLocal은 상위 스레드와 하위 스레드 간의 공유 데이터를 보장합니다.
  8. ThreadLocal 사용을 위한 애플리케이션 시나리오 및 참고 사항

1. ThreadLocal이란 무엇이며 ThreadLocal을 사용하는 이유는 무엇입니까?

ThreadLocal이란 무엇입니까?

ThreadLocal, 스레드 로컬 변수입니다. 변수를 생성하면 ThreadLocal이 변수에 액세스하는 각 스레드는 변수의 로컬 복사본을 갖게 됩니다. 여러 스레드가 이 변수에 대해 작업을 수행할 때 실제로는 자체 로컬 메모리의 변수에 대해 작업을 수행하므로 스레드 격리가 이루어집니다. 기능, 회피 동시 시나리오의 스레드 안전 문제.

// 创建一个ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();

ThreadLocal을 사용하는 이유

동시 시나리오에서는 여러 스레드가 공유 변수를 동시에 수정하는 시나리오가 있습니다. 이로 인해 선형 보안 문제가 발생할 수 있습니다 .

선형 보안 문제를 해결하기 위해 synchronized또는 와 같은 잠금 방법을 사용할 수 있습니다 Lock. 그러나 잠금 방식으로 인해 시스템 속도가 느려질 수 있습니다. 잠금의 개략도는 다음과 같습니다.

 

시간을 위해 공간을 사용하는 또 다른 솔루션이 있습니다 ThreadLocal. ThreadLocal클래스를 사용하여 공유 변수에 액세스할 때 공유 변수의 복사본은 각 스레드에 로컬로 저장됩니다. 여러 스레드가 공유 변수를 수정하면 실제로 변수의 복사본에서 작동하므로 선형 안전성이 보장됩니다.

 

2. ThreadLocal 사용 사례

일일 개발에서는 날짜 변환 도구 클래스에 자주 등장합니다. 반례를ThreadLocal 살펴보겠습니다 .

/**
 * 日期工具类
 */
public class DateUtil {

    private static final SimpleDateFormat simpleDateFormat =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static Date parse(String dateString) {
        Date date = null;
        try {
            date = simpleDateFormat.parse(dateString);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

DateUtil다중 스레드 환경에서 이 유틸리티 클래스를 실행합니다 .

public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.execute(()->{
                System.out.println(DateUtil.parse("2022-07-24 16:34:30"));
            });
        }
        executorService.shutdown();
    }

실행 후 오류를 발견했습니다.

DateUtil도구 클래스에서 추가하고 실행하면 ThreadLocal문제가 발생하지 않습니다.

/**
 * 日期工具类
 */
public class DateUtil {

    private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static Date parse(String dateString) {
        Date date = null;
        try {
            date = dateFormatThreadLocal.get().parse(dateString);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.execute(()->{
                System.out.println(DateUtil.parse("2022-07-24 16:34:30"));
            });
        }
        executorService.shutdown();
    }
}
复制代码

작업 결과:

Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
复制代码

바로 지금 반례에서 오류를 보고하는 이유는 무엇입니까? 이는 SimpleDateFormat선형적으로 안전하지 않기 때문에 공유 변수로 나타날 경우 동시 멀티스레딩 시나리오에서 오류가 보고됩니다.

추가하면 왜 문제가 없습니까 ThreadLocal? 동시 시나리오에서 ThreadLocal어떻게 보장됩니까? 다음으로 핵심 원칙을 살펴보겠습니다 ThreadLocal.

3. ThreadLocal의 원리

3.1 ThreadLocal의 메모리 구조도

매크로를 이해하기 위해 먼저 아래의 ThreadLocal메모리 구조 다이어그램을 살펴보겠습니다.

메모리 구조 다이어그램에서 다음을 볼 수 있습니다.

  • Thread클래스에는 ThreadLocal.ThreadLocalMap이라는 멤버 변수가 있습니다.
  • ThreadLocalMap배열은 내부적으로 유지되며 Entry, 각각은 Entry완전한 객체를 나타내며, keyThreadLocal자체는 일반적인 객체 값 value입니다 .ThreadLocal

3.2 핵심 소스코드 분석

여러 핵심 소스코드와 비교해보면 이해하기 쉬울듯~ 다시 클래스 소스코드로 돌아가서 Thread보면 ThreadLocalMap멤버 변수의 초기값이null

public class Thread implements Runnable {
   // ThreadLocal.ThreadLocalMap是Thread的属性
   ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocalMap핵심 소스 코드는 다음과 같습니다.

static class ThreadLocalMap {
    
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    //Entry数组
    private Entry[] table;
    
    // ThreadLocalMap的构造器,ThreadLocal作为key
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
}

ThreadLocal클래스의 주요 set()방법 :

 public void set(T value) {
        Thread t = Thread.currentThread(); //获取当前线程t
        ThreadLocalMap map = getMap(t);  //根据当前线程获取到ThreadLocalMap
        if (map != null)  //如果获取的ThreadLocalMap对象不为空
            map.set(this, value); //K,V设置到ThreadLocalMap中
        else
             createMap(t, value); //创建一个新的ThreadLocalMap
    }
   
     ThreadLocalMap getMap(Thread t) {
       return t.threadLocals; //返回Thread对象的ThreadLocalMap属性
    }

    void createMap(Thread t, T firstValue) { //调用ThreadLocalMap的构造函数
        t.threadLocals = new ThreadLocalMap(this, firstValue); this表示当前类ThreadLocal
    }
}
    

ThreadLocal클래스의 주요 get()메소드

    public T get() {
        Thread t = Thread.currentThread();//获取当前线程t
        ThreadLocalMap map = getMap(t);//根据当前线程获取到ThreadLocalMap
        if (map != null) { //如果获取的ThreadLocalMap对象不为空
            //由this(即ThreadLoca对象)得到对应的Value,即ThreadLocal的泛型值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value; 
                return result;
            }
        }
        return setInitialValue(); //初始化threadLocals成员变量的值
    }
    
     private T setInitialValue() {
        T value = initialValue(); //初始化value的值
        Thread t = Thread.currentThread(); 
        ThreadLocalMap map = getMap(t); //以当前线程为key,获取threadLocals成员变量,它是一个ThreadLocalMap
        if (map != null)
            map.set(this, value);  //K,V设置到ThreadLocalMap中
        else
            createMap(t, value); //实例化threadLocals成员变量
        return value;
    }

그렇다면 ThreadLocal의 구현 원칙에 어떻게 답해야 할까요 ? 다음과 같이 위의 구조도와 함께 설명하는 것이 가장 좋습니다~

  • Thread스레드 클래스에는 유형의 ThreadLocal.ThreadLocalMap인스턴스 변수가 있습니다 threadLocals. 즉, 각 스레드는 자체 인스턴스 변수 중 하나를 가집니다 ThreadLocalMap.
  • ThreadLocalMap배열은 내부적으로 유지되며 Entry각각은 Entry완전한 객체를 나타내며 keyThreadLocal자체는 일반적인 값 value입니다 .ThreadLocal
  • 동시 다중 스레드 시나리오에서 각 스레드는 Thread값을 ThreadLocal설정할 ThreadLocalMap자체 메모리에 저장하고 각 스레드에 대한 참조를 참조로 사용하여 ThreadLocal자체 메모리에서 map해당 값을 찾아 스레드 격리를key 달성합니다 .

이러한 핵심 방법을 이해한 후 일부 친구는 의문을 가질 수 있습니다. 왜 키로 ThreadLocalMap사용해야 합니까 ? ThreadLocal직접 사용하는 것과 线程Id다른가요 ?

4. 스레드 ID를 ThreadLocalMap의 키로 직접 사용하지 않는 이유는 무엇입니까?

예를 들어 코드는 다음과 같습니다.

public class TianLuoThreadLocalTest {

    private static final ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
    private static final ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
 
}

이 시나리오: 사용 클래스에는 두 개의 공유 변수가 있습니다. 즉, 두 개의 ThreadLocal멤버 변수가 사용됩니다. 스레드가 id사용되는 ThreadLocalMap경우 key어떤 멤버 변수를 구별하는 방법은 무엇입니까 ThreadLocal? ThreadLocal따라서 여전히 Key. 각 개체는 속성 으로 고유하게 구분ThreadLocal 될 수 있으며 각 ThreadLocal 개체는 개체 이름으로 고유하게 구분될 수 있습니다( 아래 예 참조 ). 코드를 살펴보십시오 .threadLocalHashCodeThreadLocal

public class ThreadLocal<T> {
  private final int threadLocalHashCode = nextHashCode();
  
  private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
  }
}

그런 다음 다음 코드 예제를 살펴보겠습니다.

public class TianLuoThreadLocalTest {

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable(){
            public void run(){
                ThreadLocal<TianLuoDTO> threadLocal1 = new ThreadLocal<>();
                threadLocal1.set(new TianLuoDTO("Hello 七度"));
                System.out.println(threadLocal1.get());
                ThreadLocal<TianLuoDTO> threadLocal2 = new ThreadLocal<>();
                threadLocal2.set(new TianLuoDTO("你好:ccc"));
                System.out.println(threadLocal2.get());
            }});
        t.start();
    }

}
// 运行结果
TianLuoDTO{name='Hello 七度'}
TianLuoDTO{name='你好:ccc'}

이 그림과 비교하면 더 명확할 수 있습니다.

5. TreadLocal이 메모리 누수를 일으키는 이유는 무엇입니까?

5.1 약한 참조로 인한 메모리 누수는 어떻습니까?

먼저 TreadLocal의 참조 다이어그램을 살펴보겠습니다.

ThreadLocal 메모리 누수와 관련하여 인터넷에서 가장 많이 사용되는 진술은 다음과 같습니다.

ThreadLocalMap약한 참조는 변수를 수동으로 설정할 , 참조할 외부 강한 참조가 없을 때 시스템 GC 시 재활용 해야 합니다 ThreadLocal. 이 경우 중간 에 개체가 있을 것이고 이러한 개체에 액세스 할 수 있는 방법이 없습니다 . 현재 스레드가 오랫동안 종료되지 않으면(예: 스레드 풀의 코어 스레드 ) 항상 이러한 개체 :에 대한keyThreadLocalnullThreadLocalThreadLocalThreadLocalMapkeynullEntrykeynullEntryvaluekeynullEntryvalue

ThreadLocal 변수를 수동으로 null참조 체인 다이어그램으로 설정한 경우:

사실, ThreadLocalMap이 상황은 의 설계에서 고려되었습니다. 따라서 몇 가지 보호 조치도 추가됩니다. 즉 ThreadLocal, get, set, 메서드는 스레드의 모든 작업을 remove지웁니다 .ThreadLocalMapkeynullvalue

소스 코드에는 다음과 같은 방법이 반영 ThreadLocalMap됩니다 set.

  private void set(ThreadLocal<?> key, Object value) {

      Entry[] tab = table;
      int len = tab.length;
      int i = key.threadLocalHashCode & (len-1);

      for (Entry e = tab[i];
            e != null;
            e = tab[i = nextIndex(i, len)]) {
          ThreadLocal<?> k = e.get();

          if (k == key) {
              e.value = value;
              return;
          }

           //如果k等于null,则说明该索引位之前放的key(threadLocal对象)被回收了,这通常是因为外部将threadLocal变量置为null,
           //又因为entry对threadLocal持有的是弱引用,一轮GC过后,对象被回收。
            //这种情况下,既然用户代码都已经将threadLocal置为null,那么也就没打算再通过该对象作为key去取到之前放入threadLocalMap的value, 因此ThreadLocalMap中会直接替换调这种不新鲜的entry。
          if (k == null) {
              replaceStaleEntry(key, value, i);
              return;
          }
        }

        tab[i] = new Entry(key, value);
        int sz = ++size;
        //触发一次Log2(N)复杂度的扫描,目的是清除过期Entry  
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
          rehash();
    }

ThreadLocal 메서드와 같은 get:

  public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //去ThreadLocalMap获取Entry,方法里面有key==null的清除逻辑
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
             return e;
        else
          //里面有key==null的清除逻辑
          return getEntryAfterMiss(key, i, e);
    }
        
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            // Entry的key为null,则表明没有外部引用,且被GC回收,是一个过期Entry
            if (k == null)
                expungeStaleEntry(i); //删除过期的Entry
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

5.2 핵심은 약한 참조입니다. GC 재활용이 ThreadLocal의 정상적인 작업에 영향을 미칩니까?

이 시점에서 일부 친구들은 의문을 가질 수 있습니다. ThreadLocal그것은 약한 참조key 이기 때문에 GC가 무분별하게 재활용하여 정상적인 사용에 영향을 미칠까요 ?keyThreadLocal

  • 약한 참조 : 약한 참조가 있는 객체는 수명이 더 짧습니다. 객체에 약한 참조만 있는 경우 다음 GC는 객체를 재활용합니다 (현재 메모리 공간이 충분한지 여부에 관계없이).

사실, 아니오, ThreadLocal变量참조되기 때문에 수동으로 제거하지 않는 한 GC에서 재활용하지 않습니다. ThreadLocal变量设置为null데모를 실행하여 이를 확인할 수 있습니다.

  public class WeakReferenceTest {
    public static void main(String[] args) {
        Object object = new Object();
        WeakReference<Object> testWeakReference = new WeakReference<>(object);
        System.out.println("GC回收之前,弱引用:"+testWeakReference.get());
        //触发系统垃圾回收
        System.gc();
        System.out.println("GC回收之后,弱引用:"+testWeakReference.get());
        //手动设置为object对象为null
        object=null;
        System.gc();
        System.out.println("对象object设置为null,GC回收之后,弱引用:"+testWeakReference.get());
    }
}
运行结果:
GC回收之前,弱引用:java.lang.Object@7b23ec81
GC回收之后,弱引用:java.lang.Object@7b23ec81
对象object设置为null,GC回收之后,弱引用:null

결론은 친구야 이 의심은 버려, 하하~

5.3 ThreadLocal 메모리 누수 데모

메모리 누수의 다음 예를 보여드리자면, 사실 쓰레드 풀을 이용해서 계속해서 객체를 집어넣는 것입니다.

public class ThreadLocalTestDemo {

    private static ThreadLocal<TianLuoClass> tianLuoThreadLocal = new ThreadLocal<>();


    public static void main(String[] args) throws InterruptedException {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());

        for (int i = 0; i < 10; ++i) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("创建对象:");
                    TianLuoClass tianLuoClass = new TianLuoClass();
                    tianLuoThreadLocal.set(tianLuoClass);
                    tianLuoClass = null; //将对象设置为 null,表示此对象不在使用了
                   // tianLuoThreadLocal.remove();
                }
            });
            Thread.sleep(1000);
        }
    }

    static class TianLuoClass {
        // 100M
        private byte[] bytes = new byte[100 * 1024 * 1024];
    }
}


创建对象:
创建对象:
创建对象:
创建对象:
Exception in thread "pool-1-thread-4" java.lang.OutOfMemoryError: Java heap space
	at com.example.dto.ThreadLocalTestDemo$TianLuoClass.<init>(ThreadLocalTestDemo.java:33)
	at com.example.dto.ThreadLocalTestDemo$1.run(ThreadLocalTestDemo.java:21)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

실행 결과에 OOM이 나왔지만 tianLuoThreadLocal.remove();추가하고 나면 OOM.

创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
......
复制代码

여기에서 변수를 수동으로 설정하지 않지만 여전히 메모리 누수가 발생합니다 . 스레드 풀을 사용하기 때문에 스레드 풀의 수명 주기가 길기 때문에 스레드 풀은 설정된 참조가 여전히 존재 하더라도 항상 객체의 값을 보유합니다. 마치 각 개체를 목록 에 넣은 다음 별도로 설정하는 것과 같습니다 . 이유는 동일하며 목록의 개체는 여전히 존재합니다.tianLuoThreadLocalnulltianLuoClassvaluetianLuoClass = null;objectlistobjectnull

    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        Object object = new Object();
        list.add(object);
        object = null;
        System.out.println(list.size());
    }
    //运行结果
    1

그래서 이렇게 메모리 누수가 발생했고, 결국 메모리가 제한되어 버렸습니다 OOM. 추가하면 threadLocal.remove();메모리 누수가 없습니다. 왜? threadLocal.remove();클리어되기 때문에 Entry소스 코드는 다음과 같습니다.

    private void remove(ThreadLocal<?> key) {
      Entry[] tab = table;
      int len = tab.length;
      int i = key.threadLocalHashCode & (len-1);
      for (Entry e = tab[i];
          e != null;
          e = tab[i = nextIndex(i, len)]) {
          if (e.get() == key) {
              //清除entry
              e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

어떤 친구들은 메모리 누수가 반드시 약한 참조로 인한 것은 아닌데 왜 약한 참조로 설계해야 합니까? 살펴보겠습니다.

6. Key of Entry를 약한 참조로 설계해야 하는 이유는 무엇입니까?

소스 코드를 통해 우리는 Entry그것이 Key약한 참조( 약한 참조가 사용됨 ThreadLocalMap) 로 설계되었음을 알 수 있습니다 . 약한 참조로 설계된 이유는 무엇입니까?ThreadLocalKey

 

먼저 네 가지 참조를 상기해 보겠습니다.

  • 강한 참조 : 우리는 일반적으로 new개체가 강한 참조라는 것을 알고 있습니다.예를 들어 Object obj = new Object();메모리가 부족한 경우에도 JVM은 이 개체를 재활용하는 것보다 OutOfMemory 오류를 발생시킵니다.
  • 소프트 참조 : 개체에 소프트 참조만 있는 경우 메모리 공간이 충분하고 가비지 수집기가 이를 회수하지 않으며, 메모리 공간이 부족하면 이러한 개체의 메모리가 회수됩니다.
  • 약한 참조 : 약한 참조가 있는 객체는 수명이 더 짧습니다. 개체에 대해 약한 참조만 있는 경우 다음 GC는 현재 메모리 공간이 충분한지 여부에 관계없이 개체를 재활용합니다 .
  • 팬텀 참조 : 개체에 팬텀 참조만 있는 경우 참조가 없는 것처럼 가비지 수집기에 의해 언제든지 회수될 수 있습니다. 팬텀 참조는 주로 가비지 수집기에 의해 회수되는 개체의 활동을 추적하는 데 사용됩니다.

먼저 공식 문서를 살펴보겠습니다. 약한 참조로 설계되어야 하는 이유는 다음과 같습니다.

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key。

ThreadLocal의 참조 다이어그램을 이동하겠습니다.

 

상황별로 토론해 보자:

  • Key강한 참조를 사용하는 경우 : ThreadLocal개체가 재활용되었지만 ThreadLocalMap여전히 ThreadLocal강한 참조를 보유하고 있는 경우 수동으로 삭제하지 않으면 ThreadLocal이 재활용되지 않고 Entry의 메모리 누수 문제가 발생합니다.
  • Key약한 참조를 사용하는 경우 : ThreadLocal객체가 재활용될 때 ThreadLocalMapThreadLocal에 대한 약한 참조를 보유하고 있기 때문에 수동으로 삭제하지 않더라도 ThreadLocal이 재활용됩니다. value다음 ThreadLocalMap호출 시 set,get,remove지워집니다 .

따라서 약한 참조를 사용하면 Entry추가 Key보호 계층을 제공할 수 있습니다. 약한 참조는 ThreadLocal쉽게 메모리를 누수하지 않으며 해당 항목은 value다음 ThreadLocalMap호출 에서 set,get,remove지워집니다 .

사실 메모리 누수의 근본 원인은 더 이상 사용되지 않는 메모리가 Entry스레드에서 제거되지 않았기 때문입니다 ThreadLocalMap. 일반적으로 더 이상 사용하지 않는 것을 삭제하는 Entry두 가지 방법이 있습니다 .

  • 하나는 사용 후 ThreadLocal수동으로 호출하여 remove()삭제 Entry从ThreadLocalMap하는 것 입니다.
  • 또 다른 방법은 다음과 같습니다: ThreadLocalMap만기를 지우는 자동 청산 메커니즘 Entry.( ThreadLocalMap매번 만기 청산을 get(),set()트리거합니다 )Entry

7. InheritableThreadLocal은 상위 스레드와 하위 스레드 간의 공유 데이터를 보장합니다.

우리는 ThreadLocal스레드가 분리되어 있다는 것을 알고 있습니다. 부모 스레드와 자식 스레드가 데이터를 공유하려면 어떻게 해야 합니까? 사용할 수 있습니다 InheritableThreadLocal. 먼저 살펴보겠습니다 demo.

public class InheritableThreadLocalTest {

   public static void main(String[] args) {
       ThreadLocal<String> threadLocal = new ThreadLocal<>();
       InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

       threadLocal.set("你好,七度");
       inheritableThreadLocal.set("你好,七度");

       Thread thread = new Thread(()->{
           System.out.println("ThreadLocal value " + threadLocal.get());
           System.out.println("InheritableThreadLocal value " + inheritableThreadLocal.get());
       });
       thread.start();
       
   }
}
// 运行结果
ThreadLocal value null
InheritableThreadLocal value 你好,七度

자식 스레드에서 부모 스레드의 InheritableThreadLocal 유형 있지만 ThreadLocal 유형 변수 값은 얻을 수 없음을 알 수 있습니다.

유형의 값을 얻을 수 없다는 것을 이해할 수 있습니다 ThreadLocal . 스레드가 분리되어 있기 때문입니다. InheritableThreadLocal 어떻게 이루어지나요? 원리는 무엇입니까?

클래스 에는 Thread멤버 변수 외에도 threadLocals다른 멤버 변수가 있습니다 inheritableThreadLocals. 이 두 가지 유형은 동일합니다.

public class Thread implements Runnable {
   ThreadLocalMap threadLocals = null;
   ThreadLocalMap inheritableThreadLocals = null;
 }

Thread클래스의 메서드에는 init초기화 설정 섹션이 있습니다.

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
      
        ......
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

완료되지parent的inheritableThreadLocals 않은 경우 이전 스레드에 할당 됨을 알 수 있습니다 . 직설적으로 말하면, 현재 쓰레드가 아무 것도 하지 않는다면 , 다른 쓰레드와 마찬가지로 부모 쓰레드에서 하나를 복사하지만 , 데이터는 부모 쓰레드에서 옵니다. 관심 있는 친구는 소스 코드를 공부할 수 있습니다~nullparentinheritableThreadLocalsinheritableThreadLocalsinheritableThreadLocalsnullThreadLocal

8. ThreadLocal 사용을 위한 애플리케이션 시나리오 및 참고 사항

ThreadLocal매우 중요한 점은 사용 후 수동으로 호출해야 한다는 것입니다 remove().

애플리케이션 ThreadLocal시나리오에는 주로 다음이 포함됩니다.

  • 날짜 도구 클래스를 사용하고 사용하는 경우 SimpleDateFormatThreadLocal을 사용하여 선형 안전을 보장합니다.
  • 사용자 정보를 전역적으로 저장(사용자 정보가 저장되므로 ThreadLocal현재 스레드가 필요한 곳 ​​어디에서나 사용할 수 있음)
  • 동일한 스레드를 보장하고 획득한 데이터베이스 연결은 Connection동일하며 이를 사용하여 ThreadLocal스레드 안전 문제를 해결합니다.
  • MDC로그 정보를 저장하기 위해 사용합니다 .

추천

출처blog.csdn.net/gongzi_9/article/details/126754648