1概述
線程本地變量(ThreadLocal)位每一個線程創建一個變量副本,從而線程就可以單獨使用自己擁有的變量副本,而相互之間不產生影響。而線程本地變量與線程同步機制中的共享變量有什麼區別呢?很明顯,每個線程對共享變量的修改對於其餘線程是可見的,而每個線程對線程本地變量的修改對於其餘線程是不可見的。
2示例
package com.liutao.concurrent;
public class ThreadLocalDemo {
public static ThreadLocal<Integer> integerThreadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
int num;
for (int i = 0; i < 3;i++){
num = ThreadLocalDemo.integerThreadLocal.get();
System.out.println(Thread.currentThread().getName()+":"+num);
ThreadLocalDemo.integerThreadLocal.set(num+1);
}
}
}
執行結果:
Thread-0:0
Thread-1:0
Thread-2:0
Thread-1:1
Thread-0:1
Thread-1:2
Thread-2:1
Thread-2:2
Thread-0:2
我們可以看見上面三個線程執行的內容是相同的,相互之間沒有任何影響。
針對ThreadLocal常用的四個方法如下:
- get:返回當前線程中變量副本的值。
- set:設置當前線程中變量副本的值。
- remove:移除當前線程中變量副本的值。
- initialValue:返回變量副本的初始值。當線程第一次調用變量副本的時候將會調用這個方法,除非在調用變量副本的get方法之前已經調用了set方法進行設置。當然上面的示例我們可以看見,我們重寫了initialValue方法。
3源碼分析
3.1ThreadLocalMap源碼分析
通過查看ThreadLocal的set、get方法我們可以發現ThreadLocal的實現主要是依靠了ThreadLocalMap這個內部類。那麼首先我們來看ThreadLocalMap這個內部類的實現。
通過查看源碼我們可以發現ThreadLocalMap是使用Entry來進行key和value的存儲的。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 和線程產生關聯的值 */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
這個Entry繼承自WeakReference,並使用ThreadLocal作爲key。可以看見針對弱引用,當key爲null的時候將被GC回收。
那麼針對ThreadLocalMap我們僅僅分析兩個核心方法即可。
/**
* 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;
//根據ThreadLocal的hashcode查找對應元素在數組中的位置
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;
}
//key == null,但是存在值(因爲此處的e != null),說明之前的ThreadLocal對象已經被回收了
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//ThreadLocal對應的key不存在,也沒有Entry值,則創建一個新的
tab[i] = new Entry(key, value);
int sz = ++size;
//cleanSomeSlots清楚key == null 的Entry,如果沒有清除成功,並且數組中的元素大於了閥值則refresh
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
針對上面的源碼,我們可以看見ThreadLocalMap初始的時候有一個table,並且初始容量爲16。
/**
* 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.
*/
private Entry[] table;
nextIndex方法的源碼如下:
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
可以發現輪詢table獲取的Entry的最大索引爲len-1,當大於len-1的時候索引爲0。
針對cleanSomeSlots和refresh,我們可以看見,最終都是調用expungeStableEntry來刪除Entry的value並置空節點。
/**
* 清除成舊的Entry(key==null)
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
private void rehash() {
expungeStaleEntries();
if (size >= threshold - threshold / 4)
resize();
}
/**
* 清除整個table的成舊數據
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
/**
* 從新hash計算從staleSlot節點到下一個空節點中的節點
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//刪除指定節點位置的值並置空節點
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
//從新計算hash,直到遇到null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//刪除節點值並置空節點
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
通過上面我們分析了ThreadLocalMap的set方法,接下來我們分析getEntry,源碼如下:
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);
}
通過key計算得到索引值過後直接從table中獲取值,如果獲取到就返回,沒有獲取到就調用getEntryAfterMiss,那麼getEntryAfterMiss又做了什麼呢?
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;
}
從i位置開始查找entry,當找到 entry不爲空,並且找到對應的key的時候,就返回entry,如果key ==null就直接清除掉位置i。
前面分析了ThreadLocalMap中的核心方法,那麼針對ThreadLocal中的方法相對來說就簡單多了。
3.2ThreadLocal源碼分析
(1)get方法
public T get() {
Thread t = Thread.currentThread();
//獲取當前線程的ThreadLocalMap
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();
}
返回當前線程中變量副本的值,如果沒有 ,就初始化。
(2)set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
設置當前線程的變量副本的值爲value,當然如果ThreadLocalMap不存在,則先創建ThreadLocalMap,然後再進行初始值設置。
針對remove和initialValue就不繼續講解了,都比較簡單。
4應用場景
最常見的ThreadLocal的使用時用來解決數據庫連接和session管理。
具體示例,後期在源碼中發現添加。