excel求和與計算器求和相差0.01(excel求和0.00)
604
2022-05-30
1、ThreadLocal簡介
ThreadLocal是一個全局對象,ThreadLocal是線程范圍內
變量共享
的解決方案;ThreadLocal可以看作是一個map集合,key就是當前線程,value就是要存放的變量。eg如下:
// 聲明一個ThreadLocal實例 ThreadLocal threadLocal = new ThreadLocal(); // 隨機獲取一個數字n int n = new Random().nextInt(); // 存放數據n給threadLocal: threadLocal.set(data); // 從threadLocal中取出數據n threadLocal.get();
1
2
3
4
5
6
7
8
ThreadLocal的引用變量用完后會自動給你銷毀,而不用考慮ThreadLocal中的變量會占用空間。
在ThreadLocal類中有一個Map,用于存儲每一個線程的變量副本,Map中元素的key為線程對象,value為對應的線程的變量副本。
ThreadLocal的作用:
ThreadLocal通過為每一個線程提供一個獨立的變量副本解決了變量并發訪問的沖突問題。
傳遞數據:可以通過ThreadLocal在同一線程的不同組件中傳遞公共變量。
線程隔離:每個線程的變量都是獨立的,不會相互影響。
2、入門案例
/**ThreadLocal通過為每一個線程提供一個獨立的變量副本解決了變量并發訪問的沖突問題。 * @author csp1999 * @date 2021/02/21 */ public class ThreadLocalTest { /** * 原子整數,一個分配給線程的 Thread ID */ private static final AtomicInteger nextId = new AtomicInteger(0); /** * 每一個線程對應一個 Thread ID */ private static final ThreadLocal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
運行結果如下:
當前線程名稱為:線程1,分配的Id為:0 當前線程名稱為:線程2,分配的Id為:1 當前線程名稱為:線程3,分配的Id為:2 當前線程名稱為:線程4,分配的Id為:3 當前線程名稱為:線程5,分配的Id為:4
1
2
3
4
5
由上面這個入門案例,可以看出:ThreadLocal每個線程的變量(nextId)都是獨立的,不會相互影響。
3、源碼分析
一、成員屬性
// threadLocalHashCode ---> 用于threadLocals的桶位尋址: // 1.線程獲取threadLocal.get()時: // 如果是第一次在某個threadLocal對象上get,那么就會給當前線程分配一個value, // 這個value 和 當前的threadLocal對象被包裝成為一個 entry // 其中entry的 key 是threadLocal對象,value 是threadLocal對象給當前線程生成的value // 2.這個entry存放到當前線程 threadLocals 這個map的哪個桶位呢? // 桶位尋址與當前 threadLocal對象的 threadLocalHashCode有關系: // 使用 threadLocalHashCode & (table.length - 1) 計算結果得到的位置就是當前 entry 需要存放的位置。 private final int threadLocalHashCode = nextHashCode(); // nextHashCode: 表示hash值 // 創建ThreadLocal對象時會使用到該屬性: // 每創建一個threadLocal對象時,就會使用 nextHashCode 分配一個hash值給這個對象。 private static AtomicInteger nextHashCode = new AtomicInteger(); // HASH_INCREMENT: 表示hash值的增量~ // 每創建一個ThreadLocal對象,ThreadLocal.nextHashCode的值就會增長HASH_INCREMENT(0x61c88647)。 // 這個值很特殊,它是斐波那契數也叫黃金分割數。 // hash增量為這個數字,帶來的好處就是hash分布非常均勻。 private static final int HASH_INCREMENT = 0x61c88647; /** * 返回一個nextHashCode的hash值: * 創建新的ThreadLocal對象時,使用這個方法,會給當前對象分配一個hash值。 */ private static int nextHashCode() { // 每創建一個對象,nextHashCode計算得到的hash值就增長HASH_INCREMENT(0x61c88647) return nextHashCode.getAndAdd(HASH_INCREMENT); } /* * 初始化一個起始value: * 默認返回null,一般情況下都是需要重寫這個方法的(例如第2小節的入門案例中就重寫了該方法)。 */ protected T initialValue() { return null; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
二、構造方法
一個空參構造,什么也沒做,不再分析,繼續往下學習!
public ThreadLocal() { }
1
2
三、成員方法
/** * 返回當前線程與當前ThreadLocal對象相關聯的線程局部變量,這個變量只有當前線程能訪問 * 如果當前線程沒有分配局部變量,則使用 initialValue方法去分配初始局部變量值! */ public T get() { // 獲取當前線程 Thread t = Thread.currentThread(); // getMap(t):獲取到當前線程Thread對象的ThreadLocalMap對象 ThreadLocalMap map = getMap(t); // 如果條件成立:說明當前線程已經擁有自己的ThreadLocalMap對象了 if (map != null) { // key:當前threadLocal對象(this) // 根據key調用map.getEntry()方法,獲取threadLocalMap中該threadLocal關聯的entry ThreadLocalMap.Entry e = map.getEntry(this); // 如果條件成立(當前獲取的entry不為空): // 說明當前線程初始化過與當前threadLocal對象相關聯的線程局部變量! if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; // 返回value值 return result; } } // 執行到這里有幾種情況? // 情況1:當前線程對應的threadLocalMap是空 // 情況2:當前線程與當前threadLocal對象沒有生成過相關聯的線程局部變量.. // setInitialValue方法初始化當前線程與當前threadLocal對象相關聯的線程局部變量value值, // 且當前線程如果沒有threadLocalMap的話,還會初始化創建map! return setInitialValue(); } // 獲取當前線程t的ThreadLocalMap對象 // ThreadLocalMap(位于Thread類中) ThreadLocalMap getMap(Thread t) { // 返回當前線程的 threadLocals return t.threadLocals; } /** * setInitialValue方法初始化當前線程與當前threadLocal對象相關聯的線程局部變量value值, * 且當前線程如果沒有threadLocalMap的話,還會初始化創建map! * @return the initial value */ private T setInitialValue() { // 調用的當前ThreadLocal對象的initialValue方法,這個方法大部分情況下咱們都會重寫。 // value值就是當前ThreadLocal對象與當前線程相關聯的線程局部變量。 T value = initialValue(); // 獲取當前線程對象 Thread t = Thread.currentThread(); // 獲取當前線程內部的threadLocals(threadLocalMap對象) ThreadLocalMap map = getMap(t); // 如果條件成立:說明當前線程內部已經初始化過threadLocalMap對象了(線程的threadLocals只會初始化一次) if (map != null) // 向ThreadLocalMap中保存當前threadLocal與當前線程生成的線程局部變量。 // key: 當前threadLocal對象 // value:線程與當前threadLocal相關的局部變量 map.set(this, value); // 如果執行到else ---> 說明當前線程內部threadLocalMap對象還沒有初始化過: else // 這里調用createMap方法給當前線程創建ThreadLocalMap對象: // 參數1:當前線程t // 參數2:線程與當前threadLocal相關的局部變量 createMap(t, value); // 返回線程與當前threadLocal相關的局部變量 return value; } /** * 創建當前線程的ThreadLocalMap對象 */ void createMap(Thread t, T firstValue) { // 傳遞t的意義就是要訪問當前這個線程 t.threadLocals字段,給這個字段初始化: // new ThreadLocalMap(this, firstValue): // 創建一個ThreadLocalMap對象,初始k-v為: // key:this <當前threadLocal對象> // value:線程與當前threadLocal相關的局部變量 t.threadLocals = new ThreadLocalMap(this, firstValue); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/** * 修改當前線程與當前threadLocal對象相關聯的線程局部變量: */ public void set(T value) { // 獲取當前線程 Thread t = Thread.currentThread(); // 獲取當前線程的threadLocalMap對象 ThreadLocalMap map = getMap(t); // 如果條件成立:說明當前線程的threadLocalMap已經初始化過了 if (map != null) // 調用threadLocalMap.set方法進行重寫或者添加: map.set(this, value); // 如果執行到else ---> 說明當前線程內部threadLocalMap對象還沒有初始化過: else // 這里調用createMap方法給當前線程創建ThreadLocalMap對象: // 參數1:當前線程t // 參數2:線程與當前threadLocal相關的局部變量 createMap(t, value); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** * 移除當前線程與當前threadLocal對象相關聯的線程局部變量: */ public void remove() { // 獲取當前線程的threadLocalMap對象 ThreadLocalMap m = getMap(Thread.currentThread()); // 如果條件成立:說明當前線程已經初始化過threadLocalMap對象了 if (m != null) // 調用threadLocalMap.remove( key = 當前threadLocal)移除線程局部變量 m.remove(this); }
1
2
3
4
5
6
7
8
9
10
11
4、小節
如果單看ThreadLocal其內部的相關方法,成員屬性等,其實難度并不大,但是ThreadLocal并不止于此,其真正的內核為Thread類中的ThreadLocalMap(比較復雜)。
ThreadLocalMap的源碼分析留在下一篇文章中介紹!
在學習ThreadLocalMap內核前,先來梳理一下ThreadLocal的執行流程:
流程總結:
每個線程都有自己的ThreadLocalMap對象;(ThreadLocal 多線程下資源隔離的根本原因)。
各個線程在調用同一個ThreadLocal對象的set(value)設置值的時候,是往各自的ThreadLocalMap對象數組中設置值。
至于當前值放置在數組中的下標位置,則是通過ThreadLocal對象的threadLocalHashCode計算而來。即多線程環境下ThreadLocal對象的threadLocalHashCode是共享的。
ThreadLocal對象的threadLocalHashCode是一個原子自增的變量,通過類方法initValue初始化值。
即:當實例化ThreadLocal對象ThreadLocal local = new ThreadLocal();時,就會初始化threadLocalHashCode的值,這個值不會再變。所以,同一個線程在同一個ThreadLocal對象中set()值,只能保存最后一次set的值。
為什么每個線程都有自己的ThreadLocalMap對象,且是一個數組呢?
答:根據以上的分析,多個線程操作一個ThreadLocal對象就能達到線程之間資源隔離。而采用數組是因為可能一個線程需要通過多個ThreadLocal對象達到多個資源隔離。每個不同的ThreadLocal對象的threadLocalHashCode都不一樣,也就映射到ThreadLocalMap對象數組下的不同下標。
每個線程的ThreadLocalMap對象是通過偏移位置的方式解決hash碰撞。
每個線程都有自己的ThreadLocalMap對象也有擴容機制,且是天然線程安全的。
任務調度 數據結構
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。