자바 의 ThreadLocal 이해
10289 단어 JavaThreadLocal
면접 을 볼 때 ThreadLocal 에 관 한 지식 을 물 었 는데 대답 을 잘 못 했 어 요.
ThreadLocal 소개
ThreadLocal 은 JDK 1.2 부터 스 레 드 로 컬 변 수 를 저장 하 는 클래스 입 니 다.ThreadLocal 의 변 수 는 모든 스 레 드 에 독립 적 으로 존재 합 니 다.여러 스 레 드 가 ThreadLocal 의 변 수 를 방문 할 때 사실은 자신의 현재 스 레 드 메모리 에 있 는 변 수 를 방문 하여 변수의 스 레 드 안전 을 보장 합 니 다.
우 리 는 일반적으로 ThreadLocal 을 사용 할 때 스 레 드 에 존재 하 는 변수 경쟁 문 제 를 해결 하기 위해 서 입 니 다.사실 이런 문 제 를 해결 하려 면 보통 synchronized 를 사용 하여 자 물 쇠 를 넣 어 해결 할 생각 을 하 게 됩 니 다.
예 를 들 어 Simple DateFormat 의 스 레 드 가 안전 할 때Simple DateFormat 은 비 스 레 드 가 안전 합 니 다.format()방법 이 든 parse()방법 이 든 모두 자신의 내부 에 있 는 Calendar 류 를 사용 하 는 대상 이 있 습 니 다.format 방법 은 시간 을 설정 하 는 것 입 니 다.parse()방법 은 Calendar 의 clear()방법 을 먼저 호출 한 다음 에 Calendar 의 set()방법(할당)을 호출 했 습 니 다.만약 에 한 스 레 드 가 set()를 호출 하여 값 을 부여 하면이때 또 하나의 스 레 드 가 와 서 clear()방법 을 직접 호출 하면 이 parse()방법 이 실 행 된 결과 에 문제 가 생 길 것 이다.
해결 방법
Simple Dateformat 를 사용 하 는 방법 에 synchronized 를 추가 하면 스 레 드 의 안전 을 보장 하지만 효율 을 떨 어 뜨 립 니 다.같은 시간 에 하나의 스 레 드 만 포맷 시간 을 사용 할 수 있 습 니 다.
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static synchronized String formatDate(Date date){
return simpleDateFormat.format(date);
}
해결 방법Simple DateFormat 의 대상 을 ThreadLocal 에 넣 으 면 모든 스 레 드 에 자신의 형식 대상 의 사본 이 있 습 니 다.서로 간섭 하지 않 아 스 레 드 안전 을 보장 했다.
private static final ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static String formatDate(Date date){
return simpleDateFormatThreadLocal.get().format(date);
}
ThreadLocal 의 원리우선 ThreadLocal 을 어떻게 사용 하 는 지 살 펴 보 겠 습 니 다.
ThreadLocal<Integer> threadLocal99 = new ThreadLocal<Integer>();
threadLocal99.set(3);
int num = threadLocal99.get();
System.out.println(" :"+num);
threadLocal99.remove();
System.out.println(" Empty:"+threadLocal99.get());
실행 결과:숫자:3
숫자 Empty:null
사용 하기 가 간단 합 니 다.주로 ThreadLocal 에 변 수 를 넣 습 니 다.스 레 드 실행 과정 에서 찾 을 수 있 습 니 다.실행 이 끝 난 후에 remove 에서 떨 어 뜨리 면 됩 니 다.reove()를 호출 하지 않 으 면 현재 스 레 드 는 실행 과정 에서 변수 데 이 터 를 얻 을 수 있 습 니 다.
현재 실행 중인 스 레 드 에 넣 었 기 때문에 ThreadLocal 의 변수 값 은 현재 스 레 드 에서 만 사용 할 수 있 고 스 레 드 안전 을 보장 합 니 다(현재 스 레 드 의 하위 스 레 드 도 사실 얻 을 수 있 습 니 다).
ThreadLocal 의 set()방법 원본 을 살 펴 보 겠 습 니 다.
public void set(T value) {
//
Thread t = Thread.currentThread();
// ThreadLocalMap
ThreadLocal.ThreadLocalMap map = getMap(t);
// ThreadLocalMap , ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value); // ThreadLocalMap , , 。
}
변 수 는 모두 ThreadLocalMap 이라는 변수 에 저 장 된 것 을 보 았 습 니 다.그럼 스 레 드 로 컬 맵 은 어떻게 왔 나 요?
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public class Thread implements Runnable {
... ...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
... ...
}
위의 소스 코드 를 통 해 우 리 는 ThreadLocalMap 변 수 는 현재 실행 스 레 드 의 변수 라 는 것 을 알 게 되 었 습 니 다.그래서 ThreadLocal 에 저 장 된 데 이 터 는 모두 현재 실행 스 레 드 의 한 변수 에 넣 었 습 니 다.즉,현재 스 레 드 대상 에 저 장 된 것 입 니 다.다른 스 레 드 안 은 다른 스 레 드 대상 입 니 다.다른 스 레 드 대상 의 데 이 터 를 가 져 올 수 없 기 때문에 데 이 터 는 자 연 스 럽 게 분리 되 었 습 니 다.그럼 ThreadLocalMap 은 데 이 터 를 어떻게 저장 합 니까?
ThreadLocalMap 은 ThreadLocal 클래스 의 내부 클래스 로 클래스 의 이름 에 Map 이 있 지만 Map 인 터 페 이 스 를 실현 하지 못 하고 구조 와 Map 이 유사 할 뿐이다.
ThreadLocalMap 내 부 는 사실 Entry 배열 입 니 다.Entry 는 ThreadLocalMap 의 내부 클래스 로 Weak Reference 를 계승 하고 ThreadLocal 형식의 대상 을 Entry 의 Key 로 설정 하 며 Key 설정 을 약 한 참조 로 설정 합 니 다.
ThreadLocalMap 의 내부 데이터 구 조 는 대략 이러한 key,value 로 구 성 된 Entry 의 배열 집합 입 니 다.
진정한 맵 과 는 차이 가 있 습 니 다.링크 가 없습니다.그러면 key 의 hash 충돌 을 해결 할 때 조 치 는 HashMap 과 다 를 것 입 니 다.
하나의 스 레 드 에 여러 개의 ThreadLocal 대상 을 만 들 수 있 습 니 다.여러 개의 ThreadLocal 대상 은 여러 개의 데 이 터 를 저장 합 니 다.그러면 ThreadLocalMap 에 이 데 이 터 를 배열 로 저장 합 니 다.
구체 적 인 ThreadLocalMap 의 set()방법의 소스 코드 를 살 펴 보 겠 습 니 다.
/**
* Set the value associated with key.
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
// , key key ,
if (k == key) {
e.value = value;
return;
}
// , Entry , 。
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// , key key , , 。
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
우 리 는 set()방법 에서 볼 수 있 듯 이 처리 논 리 는 네 단계 가 있다.
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
return getEntryAfterMiss(key, i, e);
}
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
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;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
우 리 는 앞에서 말 했 듯 이 ThreadLocal 은 하나의 스 레 드 에 저 장 된 데이터 로 모든 스 레 드 는 자신의 데 이 터 를 가지 고 있 지만 실제 ThreadLocal 안의 진정한 대상 데 이 터 는 사실은 더미 안에 저 장 된 것 이 고 스 레 드 에는 대상 의 인용 만 저장 되 어 있 을 뿐이다.그리고 우리 가 사용 할 때 보통 이전 스 레 드 에서 실 행 된 방법의 상하 문 에서 ThreadLocal 의 변 수 를 공유 해 야 합 니 다.
예 를 들 어 제 메 인 스 레 드 는 어떤 방법 에서 코드 를 실행 하 는 것 입 니 다.그러나 이 방법 에는 코드 가 있 을 때 스 레 드 를 새로 만 들 었 습 니 다.이 스 레 드 에 서 는 실행 중인 방법 에서 정 의 된 ThreadLocal 의 변 수 를 사 용 했 습 니 다.이 럴 때 는 새 스 레 드 에서 외부 스 레 드 의 데 이 터 를 호출 해 야 합 니 다.이 는 스 레 드 간 공유 가 필요 합 니 다.이 피 드 부모 스 레 드 가 데 이 터 를 공유 하 는 경우 ThreadLocal 도 지원 합 니 다.
예 를 들 면:
ThreadLocal threadLocalMain = new InheritableThreadLocal();
threadLocalMain.set(" ");
Thread t = new Thread() {
@Override
public void run() {
super.run();
System.out.println( " =" + threadLocalMain.get());
}
};
t.start();
실행 결과:현재 가 져 온 변 수 는=주 스 레 드 변수 입 니 다.
위의 이러한 코드 는 하위 부모 스 레 드 가 데 이 터 를 공유 하 는 상황 을 실현 할 수 있 으 며,중요 한 것 은 Inheritable Thread Local 을 사용 하여 공유 하 는 것 입 니 다.
그렇다면 이 는 어떻게 데이터 공 유 를 실현 합 니까?
Thread 클래스 의 init()방법 에는 다음 과 같은 코드 가 있 습 니 다.
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
이 코드 는 스 레 드 를 만 들 때 현재 스 레 드 의 inheritThreadLocals 변수 와 부모 스 레 드 의 inheritThreadLocals 변수 가 비어 있 지 않 을 때 부모 스 레 드 의 inheritThreadLocals 변수 에 있 는 데 이 터 를 현재 스 레 드 의 inheritThreadLocals 변수 에 부여 한 다 는 뜻 입 니 다.ThreadLocal 메모리 누 출 문제
앞에서 도 언급 했 듯 이 ThreadLocal 의 ThreadLocalMap 에 있 는 Entry 대상 은 Weak Reference 류 를 계승 하 는 것 으로 Entry 의 key 가 약 한 인용 임 을 나타 낸다.
약 인용 은 필요 하지 않 은 대상,약 인용 대상 을 묘사 하 는 것 으로 다음 쓰레기 수집 이 발생 할 때 까지 만 생존 할 수 있다.쓰레기 수집 기 가 작업 을 시작 하면 현재 메모리 가 충분 하 든 상 관 없 이 약 한 인용 대상 만 회수 합 니 다.
이 약 한 인용 은 ThreadLocal 대상 자체 이기 때문에 스 레 드 실행 이 완료 되면 ThreadLocal 대상 은 null 이 되 고 null 의 약 한 인용 대상 은 다음 GC 에서 삭 제 됩 니 다.그러면 Entry 의 Key 메모리 공간 이 방출 되 지만 Entry 의 value 는 사용 하 는 메모리 입 니 다.만약 에 스 레 드 가 재 활용 된다 면(예 를 들 어 스 레 드 탱크 의 스 레 드)이 안의 value 값 이 점점 많아 지고 결국은 메모리 가 새 게 될 것 이다.
메모리 누 출 을 방지 하 는 방법 은 ThreadLocal 을 사용 할 때마다 다음 reove()방법 을 실행 하면 key 와 value 의 공간 을 모두 방출 할 수 있 습 니 다.
그렇다면 메모리 누 출 이 발생 하기 쉬 운 데 왜 약 한 인용 으로 설정 해 야 합 니까?
정상 적 인 상황 에 서 는 강 한 인용 이 어야 하지만 강 한 인용 관 계 를 가지 고 있 으 면 회수 되 지 않 기 때문에 스 레 드 가 재 활용 되면 Entry 의 Key 와 Value 는 회수 되 지 않 아 Key 와 Value 모두 메모리 누 출 이 발생 할 수 있 습 니 다.
이상 은 자바 에서 ThreadLocal 에 대한 이해 의 상세 한 내용 입 니 다.자바 ThreadLocal 에 관 한 자 료 는 저희 의 다른 관련 글 을 주목 하 세 요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
JPA + QueryDSL 계층형 댓글, 대댓글 구현(2)이번엔 전편에 이어서 계층형 댓글, 대댓글을 다시 리팩토링해볼 예정이다. 이전 게시글에서는 계층형 댓글, 대댓글을 구현은 되었지만 N+1 문제가 있었다. 이번에는 그 N+1 문제를 해결해 볼 것이다. 위의 로직은 이...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.