자바 병렬 프로 그래 밍 시리즈(6)-ThreadLocal 사용 및 소스 코드 분석
21278 단어 자바 다 중 루틴
//
public T get() { }
//
public void set(T value) { }
//
public void remove() { }
//
protected T initialValue() { }
ThreadLocal 사용
ThreadLocal 을 어떻게 사용 하 는 지 봅 시다.
package com.rancho945.concurrent;
public class Test {
public static void main(String[] args) {
final ThreadLocal local = new ThreadLocal();
//
local.set("hello world");
new Thread(new Runnable() {
@Override
public void run() {
// local
System.out.println("1."+Thread.currentThread().getName()+"---"+local.get());
//
local.set("hello ThreadLocal");
//
System.out.println("2."+Thread.currentThread().getName()+"---"+local.get());
//
local.remove();
//
System.out.println("3."+Thread.currentThread().getName()+"---"+local.get());
}
}).start();
//
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//
System.out.println("4."+Thread.currentThread().getName()+"---"+local.get());
}
}
실행 결과
1.Thread-0---null
2.Thread-0---hello ThreadLocal
3.Thread-0---null
4.main---hello world
이 를 통 해 알 수 있 듯 이 local 인 스 턴 스 는 두 개의 스 레 드 가 공유 되 지만 그들 사이 에 값 을 설정 하고 값 을 추출 하 는 것 은 서로 영향 을 주지 않 습 니 다.이것 이 바로 로 컬 스 레 드 변수 입 니 다.initial Value 를 어떻게 사용 하 는 지 보고 있 습 니 다.위의 예 에서...
final ThreadLocal local = new ThreadLocal();
...로 바꾸다
final ThreadLocal local = new ThreadLocal(){
@Override
protected String initialValue() {
// TODO Auto-generated method stub
return "hehe";
}
};
그러면 실행 결 과 는:
1.Thread-0---hehe
2.Thread-0---hello ThreadLocal
3.Thread-0---hehe
4.main---hello world
initialValue 를 다시 썼 다 면 스 레 드 에 값 이 설정 되 어 있 지 않 거나 제거 되 었 을 때 initialValue 를 기본 값 으로 되 돌려 줍 니 다.
소스 코드 분석
ThreadLocal 클래스 에는 내부 클래스 ThreadLocalMap 이 있 습 니 다.ThreadLocal 과 스 레 드 로 컬 변수 간 의 관 계 를 담당 합 니 다.Thread 클래스 는 ThreadLocalMap 류 의 구성원 변 수 를 가지 고 있 습 니 다.
ThreadLocal 클래스 의 멤버 변 수 를 보 세 요.
// Thread
private final int threadLocalHashCode = nextHashCode();
//
private static AtomicInteger nextHashCode = new AtomicInteger();
// , , 2^N , , 。
private static final int HASH_INCREMENT = 0x61c88647;
// 0x61c88647, 。
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
set(T value)방법
ThreadLocal 클래스 의 set 방법 보기:
public void set(T value) {
//
Thread t = Thread.currentThread();
// TheadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Thread 에서:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap 은 ThreadLocal 의 정적 내부 클래스 입 니 다.처음에 스 레 드 를 가 져 온 map 는 null 입 니 다.즉,스 레 드 의 thread Locals 는 초기 화 지연 입 니 다.스 레 드 가 ThreadLocal 기능 을 사용 하지 않 았 을 때 대상 을 만 들 지 않 고 번 거 로 움 을 피 하 며 JDK 의 깊 은 인 코딩 기 초 를 충분히 나 타 냈 습 니 다.우 리 는 먼저 createMap 의 실현 을 살 펴 보 자.
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
현재 스 레 드 를 만 드 는 threadLocals 변수 입 니 다.여기 들 어 오 는 첫 번 째 매개 변 수 는 this 입 니 다.즉,현재 threadlocal 인 스 턴 스 대상 입 니 다.ThreadLocalMap 내부 클래스 보기
/**
* The initial capacity -- MUST be a power of two.
*/
//
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
// ,Entry , threadloacl
private Entry[] table;
/**
* The number of entries in the table.
*/
//
private int size = 0;
/**
* The next size value at which to resize.
*/
// table size
private int threshold; // Default to 0
// ThreadLocal , ThreadLocal
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
//tabale , INITIAL_CAPACITY
table = new Entry[INITIAL_CAPACITY];
// 0x61c88647 , threadlocal ,
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// i threadlocal k-v
table[i] = new Entry(firstKey, firstValue);
//
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void setThreshold(int len) {
// size len
// 16, 16*2/3=10 table
threshold = len * 2 / 3;
}
현재 스 레 드 에 ThreadLocalMap 인 스 턴 스 를 만 들 고 현재 threadlocal 대상 과 넣 어야 할 값 관 계 를 ThreadLocalMap 인 스 턴 스 의 table 에 저장 하 는 것 이 주요 작업 입 니 다.만약 에 하나의 스 레 드 가 세 개의 threadlocal 대상 을 사용 했다 면 예 를 들 어:
final ThreadLocal local1 = new ThreadLocal();
final ThreadLocal local2 = new ThreadLocal();
final ThreadLocal local3 = new ThreadLocal();
new Thread(new Runnable() {
@Override
public void run() {
local1.set("ThreadLocal1");
local2.set("ThreadLocal2");
local3.set("ThreadLocal3");
}
}).start();
그러면 local 1 과'Thread Local 1'은 하나의 entry 로 스 레 드 thread Locals(ThreadlocalMap 인 스 턴 스)변수의 table 에 놓 여 있 고,local 2 와'Thread Local 2'는 하나의 entry 로 도 이 table 에 놓 여 있 으 며,local 2 와'Thread Local 2'는 하나의 entry 로 도 table 에 놓 여 있 습 니 다.물론 뒤에 있 는 두 개의 entry 는 create 방법 에 넣 은 것 이 아 닙 니 다.처음 create 를 한 후에 getMap 방법 은 null 로 돌아 가지 않 고 map.set()를 실행 하 는 것 이 라 고 생각 합 니 다.
public void set(T value) {
//
Thread t = Thread.currentThread();
// TheadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
map.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);
// table i , entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
// key key , threadlocal, ,
if (k == key) {
e.value = value;
return;
}
// key null, , k-v , ,
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// table , i 。
tab[i] = new Entry(key, value);
int sz = ++size;
// Threadlocal table , 。
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
get()방법
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// threadlocal ThreadLocalMap entry
ThreadLocalMap.Entry e = map.getEntry(this);
//
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//
return setInitialValue();
}
private T setInitialValue() {
//
T value = initialValue();
// set
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// ThreadLocal ,
protected T initialValue() {
return null;
}
이것 은 앞에서 set 전이 나 remove 가 없 으 면 initial Value 방법의 반환 값 을 되 돌려 주 는 이 유 를 설명 한다.
remove()방법
ThreadLocal 의 remove 방법:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap 을 호출 하 는 방법 을 볼 수 있 습 니 다.
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// ,
if (e.get() == key) {
// entry threadlocal
e.clear();
// table i entry
expungeStaleEntry(i);
return;
}
}
}
사고 하 다.
다음 코드 실행 이 무엇 을 출력 하 는 지 보 세 요.
package com.rancho945.concurrent;
public class Counter {
public int count = 0 ;
public void increment(){
count++;
}
}
package com.rancho945.concurrent;
public class Test {
public static void main(String[] args) {
final ThreadLocal local = new ThreadLocal();
final Counter counter = new Counter();
// , count 0
local.set(counter);
//
counter.increment();
new Thread(new Runnable() {
@Override
public void run() {
local.set(counter);
// local count
System.out.println(local.get().count);
}
}).start();
}
}
생각해 보 세 요.집행 결 과 는 무엇 입 니까?0 인가요?답 은 1.이것 은 시작 하 는 예 와 왜 다 릅 니까?혹시 가짜 ThreadLocal 을 썼 나 요?서로 다른 스 레 드 간 에 서로 영향 을 주지 않 는 다 고 했 나 요?
우 리 는 여기 가 시작 하 는 것 과 무엇이 다른 지 보 았 다.시작 하 는 예 는 메 인 스 레 드 와 서브 스 레 드 set 의 대상 이 다 르 고 여기 set 는 같은 대상 이다.
즉,threadlocal 설정 이 서로 다른 대상 스 레 드 간 에 서로 영향 을 주지 않 고 같은 대상 이 라면 서로 영향 을 줄 수 있다 는 것 이다.스 레 드 복사 본 은 set 가 같은 대상 이 아니 라 이 대상 을 복사 할 수 있다 는 뜻 입 니 다.실질 적 으로 여전히 같은 대상 이다.