머리말
일 이던 면접이던ThreadLocal
오늘은 수다를 떨자 ThreadLocal
~
- ThreadLocal이란 무엇이며 ThreadLocal을 사용하는 이유
- ThreadLocal 사용 사례
- ThreadLocal의 원리
- 스레드 ID를 ThreadLocalMap의 키로 직접 사용하지 않는 이유
- 메모리 누수가 발생하는 이유는 무엇입니까? 약한 참조 때문입니까?
- Key가 약한 참조로 설계된 이유는 무엇입니까? 강한 참조를 할 수 없습니까?
- InheritableThreadLocal은 상위 스레드와 하위 스레드 간의 공유 데이터를 보장합니다.
- 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
완전한 객체를 나타내며,key
그ThreadLocal
자체는 일반적인 객체 값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
완전한 객체를 나타내며key
그ThreadLocal
자체는 일반적인 값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 개체는 개체 이름으로 고유하게 구분될 수 있습니다( 아래 예 참조 ). 코드를 살펴보십시오 .threadLocalHashCode
ThreadLocal
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
. 이 경우 중간 에 개체가 있을 것이고 이러한 개체에 액세스 할 수 있는 방법이 없습니다 . 현재 스레드가 오랫동안 종료되지 않으면(예: 스레드 풀의 코어 스레드 ) 항상 이러한 개체 :에 대한key
ThreadLocal
null
ThreadLocal
ThreadLocal
ThreadLocalMap
key
null
Entry
key
null
Entry
value
key
null
Entry
value
ThreadLocal 변수를 수동으로 null
참조 체인 다이어그램으로 설정한 경우:
사실, ThreadLocalMap
이 상황은 의 설계에서 고려되었습니다. 따라서 몇 가지 보호 조치도 추가됩니다. 즉 ThreadLocal
, get
, set
, 메서드는 스레드의 모든 작업을 remove
지웁니다 .ThreadLocalMap
key
null
value
소스 코드에는 다음과 같은 방법이 반영 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가 무분별하게 재활용하여 정상적인 사용에 영향을 미칠까요 ?key
ThreadLocal
- 약한 참조 : 약한 참조가 있는 객체는 수명이 더 짧습니다. 객체에 약한 참조만 있는 경우 다음 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
.
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
......
复制代码
여기에서 변수를 수동으로 설정하지 않지만 여전히 메모리 누수가 발생합니다 . 스레드 풀을 사용하기 때문에 스레드 풀의 수명 주기가 길기 때문에 스레드 풀은 설정된 참조가 여전히 존재 하더라도 항상 객체의 값을 보유합니다. 마치 각 개체를 목록 에 넣은 다음 별도로 설정하는 것과 같습니다 . 이유는 동일하며 목록의 개체는 여전히 존재합니다.tianLuoThreadLocal
null
tianLuoClass
value
tianLuoClass = null;
object
list
object
null
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
) 로 설계되었음을 알 수 있습니다 . 약한 참조로 설계된 이유는 무엇입니까?ThreadLocal
Key
먼저 네 가지 참조를 상기해 보겠습니다.
- 강한 참조 : 우리는 일반적으로
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
객체가 재활용될 때ThreadLocalMap
ThreadLocal에 대한 약한 참조를 보유하고 있기 때문에 수동으로 삭제하지 않더라도 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
않은 경우 이전 스레드에 할당 됨을 알 수 있습니다 . 직설적으로 말하면, 현재 쓰레드가 아무 것도 하지 않는다면 , 다른 쓰레드와 마찬가지로 부모 쓰레드에서 하나를 복사하지만 , 데이터는 부모 쓰레드에서 옵니다. 관심 있는 친구는 소스 코드를 공부할 수 있습니다~null
parent
inheritableThreadLocals
inheritableThreadLocals
inheritableThreadLocals
null
ThreadLocal
8. ThreadLocal 사용을 위한 애플리케이션 시나리오 및 참고 사항
ThreadLocal
매우 중요한 점은 사용 후 수동으로 호출해야 한다는 것입니다 remove()
.
애플리케이션 ThreadLocal
시나리오에는 주로 다음이 포함됩니다.
- 날짜 도구 클래스를 사용하고 사용하는 경우
SimpleDateFormat
ThreadLocal을 사용하여 선형 안전을 보장합니다. - 사용자 정보를 전역적으로 저장(사용자 정보가 저장되므로
ThreadLocal
현재 스레드가 필요한 곳 어디에서나 사용할 수 있음) - 동일한 스레드를 보장하고 획득한 데이터베이스 연결은
Connection
동일하며 이를 사용하여ThreadLocal
스레드 안전 문제를 해결합니다. MDC
로그 정보를 저장하기 위해 사용합니다 .