一、前言
之前看 Android Looper 源碼時,在看到 Looper.prepare 時廓八,有個靜態(tài)成員變量:sThreadLocal:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
我們看到京景,先 get 窿冯,沒有就會去 set。
然后就很好奇的去看了下 ThreadLocal 源碼确徙,看源碼的過程中醒串,JDK又給了我一個驚喜:
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
發(fā)現了一個很奇怪的常數执桌,但不知道這個常數是怎么得來的,故在網上查找了相關資料芜赌,總算明白這個常數的含義仰挣,以及 ThreadLocal 的作用。
二缠沈、ThreadLocal
先來說說這個 ThreadLocal 是什么吧膘壶?
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).
官方注釋:ThreadLocal 提供了線程本地變量州藕,和其它的變量不同,只有每個線程自己可以 get / set悼吱,每個變量(副本)是獨立的慎框。
2.1、ThreadLocal.set
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // map的key = ThreadLocal, value 是要存儲的對象
else
createMap(t, value); // 沒有就為這個 Thread 創(chuàng)建 ThreadLocalMap 并賦值
}
ThreadLocalMap getMap(Thread t) {
// Thread 中有個 threadLocals變量
// 類型:ThreadLocal.ThreadLocalMap
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
2.2后添、ThreadLocal.get
public class ThreadLocal<T> {
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(); // 初始值為 null
}
}
2.3笨枯、ThreadLocalMap
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 初始容量,之后擴容是 2的N次方擴容
private static final int INITIAL_CAPACITY = 16;
// 存儲 key & value
private Entry[] table;
// 已使用的個數
private int size = 0;
// 當前 table.length * 2 / 3遇西,即超過三分之二就開始擴容馅精,和 HashMap的負載因子 0.75 不一樣
private int threshold;
}
因為 ThreadLocalMap 的生命周期與 Thread 一樣長(Thread 持有 threadLocals)粱檀,當你的 key( ThreadLocal )為 null 時洲敢,value 仍被 table 持有,因此茄蚯,需要主動調用 ThreadLocal 的 remove 方法來清除內存压彭。
三、魔數:0x61c88647
我們在一進入 ThreadLocal 類時渗常,就能看到一段代碼:
public class ThreadLocal<T> {
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
* (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals
* are used by the same threads, while remaining well-behaved in
* less common cases.
*/
private final int threadLocalHashCode = nextHashCode(); // 1640531527
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode = new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647; // 1640531527
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
上面的代碼注釋中也解釋到:HASH_INCREMENT 是為了讓哈希碼能均勻的分布在2的N次方的數組里壮不。
每個 ThreadLocal 都使用該散列值!
散列是什么皱碘?
散列(Hash)也稱為哈希询一,就是把任意長度的輸入,通過散列算法,變換成固定長度的輸出健蕊,這個輸出值就是散列值菱阵。
在實際使用中,不同的輸入可能會散列成相同的輸出缩功,這時也就產生了沖突晴及。
通過上文提到的 HASH_INCREMENT 再借助一定的算法,就可以將哈希碼能均勻的分布在 2 的 N 次方的數組里掂之,保證了散列表的離散度抗俄,從而降低了沖突幾率。
散列算法
- 取模法
散列長度 m, 對于一個小于 m 的數 p 取模世舰,所得結果為散列地址动雹。對 p 的選擇很重要,一般取素數或 m
公式:f(k) = k % p (p<=m)
因為求模數其實是通過一個除法運算得到的跟压,所以叫“取模法”
- 平方散列法(平方取中法)
先通過求關鍵字的平方值擴大相近數的差別胰蝠,然后根據表長度取中間的幾位數作為散列函數值。
又因為一個乘積的中間幾位數和乘數的每一位都相關震蒋,所以由此產生的散列地址較為均勻茸塞。
公式:f(k) = ((k * k) >> X) << Y
對于常見的32位整數而言,也就是 f(k) = (k * k) >> 28
- 斐波那契(Fibonacci)散列法
和平方散列法類似查剖,此種方法使用斐波那契數列的值作為乘數而不是自己钾虐。
對于 16 位整數而言,這個乘數是 40503笋庄。
對于 32 位整數而言效扫,這個乘數是 2654435769。
對于 64 位整數而言直砂,這個乘數是 11400714819323198485菌仁。公式:f(k) = ((k * 2654435769) >> X) << Y
對于常見的32位整數而言,也就是 f(k) = (k * 2654435769) >> 28
這時我們可以隱隱感覺到 0x61c88647 與斐波那契數列有些關系静暂。
- 隨機數法
選擇一隨機函數济丘,取關鍵字的隨機值作為散列地址,通常用于關鍵字長度不同的場合洽蛀。
公式:f(k) = random(k)
- 鏈地址法(拉鏈法)
懂了散列算法摹迷,我們再來了解下拉鏈法。拉鏈法是為了 HashMap 中降低沖突郊供,除了拉鏈法峡碉,還可以使用開放尋址法、再散列法颂碘、鏈地址法异赫、公共溢出區(qū)等方法。這里就只簡單介紹了拉鏈法头岔。
把具有相同散列地址的關鍵字(同義詞)值放在同一個單鏈表中塔拳,稱為同義詞鏈表。有 m 個散列地址就有 m 個鏈表峡竣,同時用指針數組 T[0..m-1] 存放各個鏈表的頭指針靠抑,凡是散列地址為 i 的記錄都以結點方式插入到以 T[i] 為指針的單鏈表中。T 中各分量的初值應為空指針适掰。
可以看出用斐波那契散列法調整之后會比原來的除法散列離散度好很多颂碧。
再來說說,魔數怎么來的类浪?
public class HashTest {
public static void main(String[] args) {
long lg = (long) ((1L << 32) * (Math.sqrt(5) - 1)/2);
System.out.println("as 32 bit unsigned: " + lg);
int i = (int) lg;
System.out.println("as 32 bit signed: " + i);
System.out.println("MAGIC = " + 0x61c88647);
}
}
// as 32 bit unsigned: 2654435769
// as 32 bit signed: -1640531527
// MAGIC = 1640531527
可以發(fā)現 0x61c88647 與一個神奇的數字產生了關系载城,它就是 (Math.sqrt(5) - 1)/2。
也就是傳說中的黃金比例 0.618(0.618 只是一個粗略值)费就,即 0x61c88647 = 2^32 * 黃金分割比诉瓦。同時也對應上了上文所提到的斐波那契散列法。
四力细、ThreadLocal 應用場景
ThreadLocal的使用場景:
- 某些對象在多線程并發(fā)訪問時可能出現問題睬澡,比如使用SimpleDataFormat的parse()方法,內部有一個Calendar對象眠蚂,調用SimpleDataFormat的parse()方法會先調用Calendar.clear()煞聪,然后調用Calendar.add(),如果一個線程先調用了add()然后另一個線程又調用了clear()逝慧,這時候parse()方法解析的時間就不對了昔脯,我們就可以用ThreadLocal<SimpleDataFormat>來解決并發(fā)修改的問題;
- 另一種場景是Spring事務馋艺,事務是和線程綁定起來的栅干,Spring框架在事務開始時會給當前線程綁定一個Jdbc Connection,在整個事務過程都是使用該線程綁定的connection來執(zhí)行數據庫操作捐祠,實現了事務的隔離性碱鳞。Spring框架里面就是用的ThreadLocal來實現這種隔離:
public abstract class TransactionSynchronizationManager {
//線程綁定的資源,比如DataSourceTransactionManager綁定是的某個數據源的一個Connection,在整個事務執(zhí)行過程中
//都使用同一個Jdbc Connection
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
//事務注冊的事務同步器
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
//事務名稱
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
//事務只讀屬性
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
//事務隔離級別
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
//事務同步開啟
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
}
}
但是不是說一遇到并發(fā)場景就用ThreadLocal來解決,我們還可以用synchronized或者鎖來實現線程安全。