什么是ThreadLocal
ThreadLocal是一個(gè)線程獨(dú)享的本地存放空間,什么是獨(dú)享的本地空間,也就是時(shí)候每個(gè)線程都有一份,
線程之間不能共享該區(qū)域。主要用于存儲(chǔ)線程變量。
什么情況下會(huì)使用ThreadLocal
多線程的情況下,如果存在資源的競(jìng)爭(zhēng)的時(shí)候,我們通常使用鎖的機(jī)制去處理這種資源的競(jìng)爭(zhēng),而對(duì)于一些資源
每個(gè)線程都要有一份的情況下,我們就會(huì)使用ThreadLocal.
例如:
- Spring的事務(wù)管理,是通過(guò)C3P0或者其他的數(shù)據(jù)庫(kù)連接池中獲取一個(gè)Connection,然后將Connection存放每個(gè)線程中
的ThreadLocal中,保證每個(gè)線程之間使用的數(shù)據(jù)庫(kù)連接都是獨(dú)享的。我們可以想下,如果每個(gè)線程都是使用同一個(gè)
數(shù)據(jù)連接,我們就無(wú)法控制事務(wù)的提交和回滾。因此每個(gè)線程必須獨(dú)占這個(gè)數(shù)據(jù)庫(kù)連接。那如果我們?cè)贒ao層
獲取Connection,然后執(zhí)行SQL提交事務(wù)。就不會(huì)存在這個(gè)問(wèn)題啦,其實(shí)并不是,我們通常會(huì)在三層中的服務(wù)層進(jìn)行事務(wù)
的開(kāi)啟和事務(wù)的提交、回滾。那么為什么會(huì)在服務(wù)層進(jìn)行事務(wù)的處理,主要是因?yàn)槭聞?wù)的邊界問(wèn)題。
因?yàn)镾ervice會(huì)調(diào)用一系列的DAO對(duì)數(shù)據(jù)庫(kù)進(jìn)行多次操作。多個(gè)Dao中我們無(wú)法確定到底哪個(gè)Dao進(jìn)行事務(wù)的提交。
因此使用ThreadLocal進(jìn)行存放每個(gè)數(shù)據(jù)庫(kù)的連接。 - 還有一種場(chǎng)景,在多數(shù)據(jù)源的情況下,我們可以通過(guò)一個(gè)key獲取對(duì)應(yīng)的數(shù)據(jù)源,而這個(gè)key值
,從我們ThreadLocal中獲取到對(duì)應(yīng)的數(shù)據(jù)源。從而隔離多線程下數(shù)據(jù)源的訪問(wèn)。
ThreadLocal的使用
- ThreadLocal的使用,我們只需要?jiǎng)?chuàng)建一個(gè)ThreadLocal并且指定存放的數(shù)據(jù)類(lèi)型即可。
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
- ThreadLocal類(lèi)的方法
public T get() 獲取當(dāng)前線程的變量
public void set(T value) 設(shè)置當(dāng)前線程的變量
public void remove() 刪除線程變量
ThreadLocal源碼分析
一、ThreadLocal結(jié)構(gòu)分析
ThreadLocal的實(shí)現(xiàn)原理,我們可以通過(guò)ThreadLocal的get方法進(jìn)行跟蹤分析。
1 public T get() {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null) {
5 ThreadLocalMap.Entry e = map.getEntry(this);
6 if (e != null) {
7 @SuppressWarnings("unchecked")
8 T result = (T)e.value;
9 return result;
10 }
11 }
12 return setInitialValue();
13 }
從ThreadLocal的get方法中我們可以看出,現(xiàn)獲取當(dāng)前的線程,再通過(guò)線程去獲取
對(duì)應(yīng)線程的ThreadLocalMap.從ThreadLocalMap的名字,我們可以看出他是一個(gè)Map的數(shù)據(jù)結(jié)構(gòu)。
我們就想ThreadLocalMap是存放在哪里的呢?我們可以通過(guò)跟中g(shù)etMap的方法中看出,ThreadLocalMap
是存放到Thread中。
1 ThreadLocalMap getMap(Thread t) {
2 return t.threadLocals;
3 }
而Thread中的ThreadLocalMap是在ThreadLocal中進(jìn)行初始化。我可以通過(guò)ThreadLocal中的set方法和setInitialValue方法查看到初始化ThreadLocalMap.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

二、ThreadLocalMap
- 1.存儲(chǔ)結(jié)構(gòu)分析
我可以從ThreadLocalMap的源碼進(jìn)行查看,ThreadLocalMap跟HashMap等都是使用Entry數(shù)據(jù)結(jié)構(gòu)進(jìn)行存儲(chǔ).
1 static class ThreadLocalMap {
2 static class Entry extends WeakReference<ThreadLocal<?>> {
3 /** The value associated with this ThreadLocal. */
4 Object value;
5
6 Entry(ThreadLocal<?> k, Object v) {
7 super(k);
8 value = v;
9 }
10 }
11
12 private Entry[] table;
那么如果我們?cè)诖a中創(chuàng)建兩個(gè)ThreadLocal會(huì)怎樣存儲(chǔ)的呢?
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1;
}
};
public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "hello";
}
};
通過(guò)對(duì)setInitialValue方法進(jìn)行分析發(fā)現(xiàn),我們創(chuàng)建兩個(gè)ThreadLocal,其實(shí)內(nèi)部是將當(dāng)前的ThreadLocal對(duì)象作為鍵值,initialValue的返回值做為Value。
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
總結(jié):
我們通過(guò)對(duì)ThreadLocalMap的set和get方法,可以發(fā)現(xiàn),我們ThreadLocalMap是存Thread中,由ThreadLocal的設(shè)置相關(guān)方法進(jìn)行創(chuàng)建。存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)是以ThreadLocal作為鍵保存對(duì)應(yīng)的Value到ThreadLocalMap中。

我們可以通過(guò)源碼查看到Entry是Key是一個(gè)弱引用,而不是像HashMap那樣使用強(qiáng)引用。那么ThreadLocalMap中的key為弱引用的,那么ThreadLcaoMap的中的值只能存活到下一次的GC。
弱引用:通常用于存儲(chǔ)非必需對(duì)象的,弱引用指向的對(duì)象實(shí)例,在下一次垃圾收集發(fā)生之前是存活的。
- ThreadLocalMap內(nèi)存泄漏
從存儲(chǔ)的結(jié)構(gòu)我們可以發(fā)現(xiàn),ThreadLocal是ThreadLocalMap的鍵值。如果在我們的代碼中將 ThreadLocalRef設(shè)置為null的情況下,我們就不可以通過(guò)ThreadLocal進(jìn)行訪問(wèn),而ThreadLocalMap還是會(huì)保存Entry,而這樣就會(huì)造成內(nèi)存的泄漏。Entry只能等待線程結(jié)束后才能進(jìn)行GC。
- ThreadLocalMap內(nèi)存泄漏
import org.junit.Test;
public class UseThreadLocal {
private static int threadCount = 30;
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1;
}
};
public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "hello";
}
};
class ThreadLocalRunable implements Runnable {
private int threadId;
public ThreadLocalRunable(int threadId) {
this.threadId = threadId;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id = "+threadLocal.get());
threadLocal2 = null;
Integer threadLocalInt = threadLocal.get();
threadLocal.set(threadLocalInt+this.threadId);
System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
}
}
@Test
public void test() throws InterruptedException {
Thread[] threads = new Thread[threadCount];
for (int i = 0 ;i<threadCount;i++){
threads[i] = new Thread(new ThreadLocalRunable(i),"線程"+i);
}
for (Thread t :threads){
t.start();
t.join();
}
}
}
為了防止內(nèi)存泄漏,我們可以通過(guò)ThreadLocal提供的remove移除ThreadlocalMap中ThreadLocal的Value。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
3.為什么ThreadLocalMap中的ThreadLocal使用弱引用
上面我們說(shuō)過(guò)當(dāng)我們?yōu)門(mén)hreadLocalRef設(shè)置為null的時(shí)候,ThreadLocal會(huì)被回收,而ThreadLocal被調(diào)用的前提是沒(méi)有被其他引用。那么在ThreadLocalMap中會(huì)將ThreadLocal作為鍵值。如果ThreadLocalMap中使用強(qiáng)引用的話,ThreadLocal就一定不會(huì)被回收,就會(huì)造成ThreadLocal的內(nèi)存泄漏,但是ThreadLocalMap使用的是弱引用,那也就是說(shuō)在下一次GC的時(shí)候ThreadLocal就會(huì)被回收,而不會(huì)造成內(nèi)存泄漏。- ThreadLocalMap內(nèi)存溢出
我們使用多線程是為了提高CPU的使用效率或者將一些耗時(shí)的工作由其他線程進(jìn)行處理。
那么如果我們線程處理的時(shí)間非常耗時(shí),存放在ThreadLocal中的數(shù)據(jù)非常大,并且存放在Threadlocal中的數(shù)據(jù)沒(méi)有釋放。JVM為了避免內(nèi)存溢出,
- ThreadLocalMap內(nèi)存溢出
運(yùn)行時(shí)設(shè)置堆內(nèi)存為1m
-Xms=1m
-Xmx=1m
public class ThreadLocalOOM {
private static int threadCount = 30;
public static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>() {
@Override
protected HashMap initialValue(){
HashMap map= new HashMap<>();
for (int i=0;i<1000000000;i++){
map.put(i,i);
}
return map;
}
};
class ThreadLocalRunable implements Runnable {
private int threadId;
public ThreadLocalRunable(int threadId) {
this.threadId = threadId;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id = "+threadLocal.get());
System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
}
}
@Test
public void test() throws InterruptedException {
Thread[] threads = new Thread[threadCount];
for (int i = 0 ;i<threadCount;i++){
threads[i] = new Thread(new ThreadLocalRunable(i),"線程"+i);
}
for (Thread t :threads){
t.start();
t.join();
}
}
}