前言
在有些時候,單個線程執行任務非常多的時候,後一個步輸入是前一個步驟的輸出,我們有時候會採用責任鏈模式,但是調用鏈路長了以後,這種傳參方式
會顯得冗餘,於是就有了線程上下文的設計,每個線程會有不同的參數實例。原因是每個線程Thread.currentThread()作爲key,這樣就可以保證線程的
獨立性。
需要注意的是,我們會一般用Map存儲,用當前的線程作爲key,當線程生命週期結束後,Map中的實例並不會釋放,因爲還是根可達的,久而久之,會產生內存溢出。
ThreadLocal 簡單介紹
在展開深入分析之前,咱們先來看一個官方示例:
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadName=" + Thread.currentThread().getName() + ",threadId=" + ThreadId.get());
}
}).start();
}
}
}
運行如下,
threadName=Thread-2,threadId=0
threadName=Thread-3,threadId=1
threadName=Thread-0,threadId=2
threadName=Thread-1,threadId=3
threadName=Thread-4,threadId=4
可以看到每個線程不會相互影響,每個線程存入threadLocal的值是完全互相獨立的。
我再把類註釋放上去
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
大概意思就是幾條:
-
它能讓線程擁有了自己內部獨享的變量
-
每一個線程可以通過get、set方法去進行操作
-
可以覆蓋initialValue方法指定線程獨享的值
-
通常會用來修飾類裏private static final的屬性,爲線程設置一些狀態信息,例如user ID或者Transaction ID
-
每一個線程都有一個指向threadLocal實例的弱引用,只要線程一直存活或者該threadLocal實例能被訪問到,都不會被垃圾回收清理掉
ThreadLocal常用方法介紹
ThreadLocal方法有如下:
ThreadLocal、
childValue、
createInheritedMap、
createMap、
get、
getMap、
initialValue、
nextHashCode、
remove、
set、
setInitialValue、
withInitial、
HASH_INCREMENT、
nextHashCode、
threadLocalHashCode
ThreadLocal常用方法有initialValue、get、set、remove。
initialValue
protected T initialValue() {
return null;
}
就是在爲每個線程變量賦一個默認值,如果沒有設的話,默認爲null。
set
就是如果沒有調用set方法,數據的初始值就是initialValue的值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
-
獲取當前現成的Thread.currentThread()
-
根據當前線程獲取到線程的ThreadLocalMap,可以看下面Thread源碼,
每個線程有一個ThreadLocalMap的引用,先記下,後面有用。
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
-
如果沒有獲取到就創建,獲取到就賦值
-
先說下這個創建map的方法,首先會new一個ThreadLocalMap,創建entry,用當前的ThreadLocal作爲key,要存的數據作爲value,結束
-
如果map存在,就遍歷entry,找到如果有相同的ThreadLocal,那就用新的值替換掉,結束。
-
如果沒有找到相同的ThreadLocal,則創建entry,用當前的ThreadLocal作爲key,要存的數據作爲value
注意,如果遍歷過程中,entry中key的值爲null,那就用新的key替代,還會根據當前的size和閾值進行比較,超過閾值,則進行key值爲null的清理。
get
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
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();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
-
獲取當前線程內部的ThreadLocalMap
-
map存在則獲取當前ThreadLocal對應的value值
-
map不存在或者找不到value值,則調用setInitialValue,進行初始化
看看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
是WeakReference
類型的,Java垃圾回收時,看一個對象需不需要回收,就是看這個對象是否可達。什麼是可達,
就是能不能通過引用去訪問到這個對象。
jdk1.2以後,引用就被分爲四種類型:強引用、弱引用、軟引用和虛引用。強引用就是我們常用的Object obj = new Object(),obj就是一個強引用,指向了對象內存空間。當內存空間不足時,Java垃圾回收程序發現對象有一個強引用,寧願拋出OutofMemory錯誤,也不會去回收一個強引用的內存空間。而弱引用,即WeakReference,意思就是當一個對象只有弱引用指向它時,垃圾回收器不管當前內存是否足夠,都會進行回收。反過來說,這個對象是否要被垃圾回收掉,取決於是否有強引用指向。ThreadLocalMap這麼做,是不想因爲自己存儲了ThreadLocal對象,而影響到它的垃圾回收,而是把這個主動權完全交給了調用方,一旦調用方不想使用,設置ThreadLocal對象爲null,內存就可以被回收掉。
看個例子
public class MemoryLeak {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
TestClass t = new TestClass(i);
t.printId();
t = null;
}
}
}).start();
}
static class TestClass{
private int id;
private int[] arr;
private ThreadLocal<TestClass> threadLocal;
TestClass(int id){
this.id = id;
arr = new int[1000000];
threadLocal = new ThreadLocal<>();
threadLocal.set(this);
}
public void printId(){
System.out.println(threadLocal.get().id);
}
}
}
運行的結果爲: java.lang.OutOfMemoryError: Java heap space
稍作修改
public class MemoryLeak {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
TestClass t = new TestClass(i);
t.printId();
t.threadLocal.remove();
}
}
}).start();
}
static class TestClass{
private int id;
private int[] arr;
private ThreadLocal<TestClass> threadLocal;
TestClass(int id){
this.id = id;
arr = new int[1000000];
threadLocal = new ThreadLocal<>();
threadLocal.set(this);
}
public void printId(){
System.out.println(threadLocal.get().id);
}
}
}
正常完成,比代碼只有一處不同:t = null改爲了t.threadLocal.remove();
我們來看下remove的源碼
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
就一句話,獲取當前線程內部的ThreadLocalMap,存在則從map中刪除這個ThreadLocal對象。
分析下爲什麼?
ThreadLocalMap.class
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry中的key指向的是ThreadLocal,說明key是弱引用,當線程的週期結束後,會自動回收,也就是key這時爲null,但是value是Object,這個是強引用,沒有辦法回收,這也是之前看set方法源碼的時候,會清除key爲null的數據,我們在
實際應用的時候記得線程結束後,調用remove方法。