一、ThreadLocal原理
如果看懂了ThreadLocal的set()方法,get()、remove()方法也就好理解了,所以重點看一下set()方法。
set()方法執行流程總結:
1.獲取當前線程對象
2.獲取當前線程對象的成員變量ThreadLocalMap
3.1不爲null,set值
3.1.1獲取當前ThreadLocalMap對象的Entry數組
3.1.2獲取ThreadLocal對象的i值
3.1.3遍歷Entry數組,根據ThreadLocal對象的i值判斷,如果ThreadLocal對象已存在數組中,更新值,退出
3.1.4不存在數組中,以key-value形式:將ThreadLocal對象與值value綁定放入數組中
3.2爲null,爲當前線程創建成員變量ThreadLocalMap
3.2.1 new一個Entry數組,初始容量爲INITIAL_CAPACITY=16
3.2.2計算出存放在Entry數組的位置i:ThreadLocal的threadLocalHashCode與15的位運算,結果與取模相同。
3.2.3放入Entry數組
源碼分析如下:
public class Thread implements Runnable {
//每個線程對象都維護了一個成員變量ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
public T get() {}
public void remove() {}
public void set(T value) {
Thread t = Thread.currentThread();//當前線程對象
ThreadLocal.ThreadLocalMap map = getMap(t);//獲取當前線程對象的成員變量ThreadLocalMap
if (map != null)
map.set(this, value);//不爲null,set值
else
createMap(t, value);//爲null,爲當前線程創建ThreadLocalMap
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);//爲線程對象創建成員變量ThreadLocalMap
}
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
static class Entry extends WeakReference<ThreadLocal<?>> {
//線程實際存儲私有數據的地方
Object value;
//以key-value形式,將ThreadLocal與Object對象綁定。一個ThreadLocal對象在一個線程Thread對象中只可存儲一個Object。
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//ThreadLocalMap類內部維護了一個Entry數組對象table
private ThreadLocal.ThreadLocalMap.Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];//new一個Entry數組,初始容量爲INITIAL_CAPACITY=16
/**
* 計算出存放在table數組的位置:ThreadLocal的threadLocalHashCode與15的位運算,結果與取模相同。
* 因爲每個ThreadLocal對象創建後,threadLocalHashCode是final不變的,所以每個ThreadLocal對象的i值是不變的,
* 這也是爲什麼一個ThreadLocal對象在一個線程Thread對象中只可存儲一個Object的原因
*/
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);//放入Entry數組
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal<?> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;//當前ThreadLocalMap對象的Entry數組
int len = tab.length;
int i = key.threadLocalHashCode & (len - 1);//ThreadLocal對象的i值
//遍歷Entry數組
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//ThreadLocal對象已存在數組中,更新值,退出
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//不存在數組中,以key-value形式:將ThreadLocal與value綁定放入數組中
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
}
}
根據上面源碼總結如下:
一個Thread對象可有多個ThreadLocal對象在線程的本地內存中存儲值,一個ThreadLocal對象可在多個線程的本地內存中存儲值。所以Thread與ThreadLocal可簡單理解爲多對多關係。(線程的本地內存也稱爲線程的工作內存,JMM內存模型定義的概念)
ThreadLocal的本質就是一個工具類,提供了set()、get()、remove()等方法,提供對線程本地內存的值的操作。Thread的成員變量ThreadLocalMap實現線程本地內存數據存儲的(重點:成員變量實現本地內存對象的存儲),ThreadLocalMap是定義在ThreadLocal的靜態內部類。
ThreadLocalMap維護一個Entry類型數組,Entry是定義在ThreadLocalMap的靜態內部類,Entry就是用了一個Object類型的成員變量value來存儲值的,而且Entry的構造函數還將本ThreadLocal對象與線程值value以key-value形式綁定了。一個ThreadLocal對應Entry[]數組的下標i是唯一不變的,因爲ThreadLocal的hash值是final。
二、ThreadLocal使用場景
Synchronized和ThreadLocal都是用來解決多線程併發數據訪問的,但是Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。所以,使用場景要根據這個變量本身是否應該是共享的!
ThreadLocal常用使用場景爲解決數據庫連接、Session管理等。可參考spring的管理。
例子:一個變量是成員變量(多線程共享的),但它本質上應該是線程私有的,就可用ThreadLocal將共享成員變量copy一份到線程私有變量中。如下Demo:
/**
* 問題:如何做到 MyThreadLocal單例,sendMessage()方法不加鎖,但是多線程執行sendMessage()仍然是線程安全的?
* 答:共享成員變量connection設爲線程私有。即使用ThreadLocal將共享成員變量變成線程私有
**/
public class MyThreadLocal {
//成員變量,多線程共享
private Connection connection;
public static ThreadLocal<Connection> threadLocal = new ThreadLocal();
public void sendMessage() throws SQLException {
//Statement statement = connection.createStatement();
Statement statement = getConnection().createStatement();
//...
}
public static Connection getConnection() {
Connection conn = threadLocal.get();
if (conn == null) {
Connection conn = ConnectionManager.getConnection();
threadLocal.set(conn);
return conn;
} else {
return conn;
}
}
}