程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 淺析ThreadLocal,threadlocal

淺析ThreadLocal,threadlocal

編輯:JAVA綜合教程

淺析ThreadLocal,threadlocal


  這是我的第一篇博客,條理不是很清晰,不過還是希望能對大家有所幫助。

  首先明確一下這個類的作用,ThreadLocal類是用來為每個線程提供了一份變量的副本,即每個線程的局部變量。每個線程都在自己的棧空間裡存有這個變量的值(對象的話就是引用),它們之間是線程隔離的,一個線程對這個ThreadLocal變量做的修改不會被其他線程看見。

下面先看一個例子。

 1 import java.util.Random;
 2 
 3 public class ThreadLocalTest {
 4     private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){
 5         protected Integer initialValue() {
 6             return 6;
 7         }
 8     };
 9     
10     public static int get(){
11         return threadId.get();
12     }
13     
14     public static void main(String[] args) {
15         new Thread(new MThread("線程A")).start();
16         new Thread(new MThread("線程B")).start();
17         new Thread(new MThread("線程C")).start();        
18     }
19     
20     static class MThread implements Runnable{
21         private String name;
22         public MThread(String str){
23             name = str;
24         }
25         public void run() {
26             int id = get();
27             System.out.println("thread "+ name +"'s Id : "+ id);
28             threadId.set(10 * new Random().nextInt(10));
29             for(int i = 0; i<3; i++){
30                 id = get();
31                 System.out.println("**thread "+ name +"'s Id : "+ id);
32             }
33         }
34     }
35 }

結果:

thread 線程B's Id : 6
**thread 線程B's Id : 60
**thread 線程B's Id : 60
**thread 線程B's Id : 60
thread 線程A's Id : 6
**thread 線程A's Id : 50
**thread 線程A's Id : 50
**thread 線程A's Id : 50
thread 線程C's Id : 6
**thread 線程C's Id : 90
**thread 線程C's Id : 90
**thread 線程C's Id : 90

  從上面的結果中我們可以看出每個線程一開始調用get()得到的值都是6,而從輸出順序可知,線程A第一次調用get()時線程B已經在28行set()過threadId,但是A得到的還是6,說明B線程的改動對A是不可見的。

  然後我們看看源碼中的注釋:

大意是說每個線程都有一份自己的,獨立於別人初始化的變量副本。下面就用源碼中的example來看看是不是獨立初始化的。

 1 import java.util.concurrent.atomic.AtomicInteger;
 2 
 3 public class ThreadLocalId {
 4     private static final AtomicInteger nextId = new AtomicInteger(0);
 5     private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){
 6         protected Integer initialValue() {
 7             return nextId.getAndIncrement();
 8         }
 9     };
10     
11     public static int get(){
12         return threadId.get();
13     }
14     
15     public static void main(String[] args) {
16         new Thread(new MThread("線程A")).start();
17         new Thread(new MThread("線程B")).start();
18         new Thread(new MThread("線程C")).start();        
19     }
20     
21     static class MThread implements Runnable{
22         private String name;
23         public MThread(String str){
24             name = str;
25         }
26         public void run() {
27             int id ;
28             for(int i = 0; i<3; i++){
29                 id = get();
30                 System.out.println("thread "+ name +"'s Id : "+ id);
31             }
32         }
33     }
34 }

結果:

thread 線程A's Id : 1
thread 線程A's Id : 1
thread 線程C's Id : 2
thread 線程C's Id : 2
thread 線程B's Id : 0
thread 線程B's Id : 0
thread 線程B's Id : 0
thread 線程A's Id : 1
thread 線程C's Id : 2

 從結果中可以看出每個線程的值都是不同的,這就是因為它們是獨立初始化的。因為它們第一次調用get()時如果之前沒有set()過值或remove()過,則會調用initialValue()方法來初始化值,而這個方法在第6行中進行了覆蓋,每次都是返回nextId的值,而nextId每次取完值都會自增,所以每個線程得到的值都會不同。上面的循環3次是為了說明源碼注釋中的這句:remains unchanged on subsequent calls,後續調用會返回不變的值(當然前提是期間沒有通過set()改變過值。。)。

這從get()的源碼中可以看出:

public T get() {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }

 

首先會得到當前調用get()方法的線程對象,然後通過getMap()方法獲取到與這個線程綁定的ThreadLocalMap對象,下面是getMap()的實現

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

 很簡單,就是獲取Thread類的成員變量 ThreadLocal.ThreadLocalMap threadLocals = null; 在Thread類中默認為null,接上面的,當Map為空時,就調用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;
    }

而在setInitialValue()中,調用了覆蓋後的initialValue()取得一個值給value,剛剛是因為Map為null而調的這個方法,所以就會執行 createMap(t, value);而這個方法就是new一個ThreadLocalMap對象賦值給參數t線程,構造參數第一個this是Threadlocal對象,用來在Entry中對應著一個value。

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

 

可以看到ThreadLocalMap中持有一個Entry的數組,而每一個Entry裡就有一個ThreadLocal對應著一個value值,再結合get()的源碼就可以知道大體思路就是:threadLocal變量的值的獲取是首先找到當前線程對象,然後在取得這個線程對象的成員變量threadLocalMap,然後再通過threadLocalMap中的Entry[]取得這個當前ThreadLocal對應的Entry對象,然後Entry.value就得到了最終的值。至於怎麼找到當前Thread對應的Entry對象就是下面的代碼:

 1 ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
 2             table = new Entry[INITIAL_CAPACITY];
 3             int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
 4             table[i] = new Entry(firstKey, firstValue);
 5             size = 1;
 6             setThreshold(INITIAL_CAPACITY);
 7         }
 8 
 9 private Entry getEntry(ThreadLocal key) {
10             int i = key.threadLocalHashCode & (table.length - 1);
11             Entry e = table[i];
12             if (e != null && e.get() == key)
13                 return e;
14             else
15                 return getEntryAfterMiss(key, i, e);
16         }

在第4行放入的時候就是通過ThreadLocal的成員threadLocalHashCode按位與上Entry數組初始化容量-1獲得的下標,所以getEntry()時,第10行先得出下標i,再table[i]就能獲取得到Entry了。

最後用自己的話說就是:每個線程都有一個ThreaLocalMap變量,這個Map裡面有一個Entry數組,裡面保存了很多個ThreadLocal和Value的映射,當你調用get()時,發現線程沒有ThreadLocalMap就創建一個ThreadLocalMap,發現Entry數組裡沒有找到這ThreadLocal對應的Entry就創建一個Entry,在把值(這個值的獲取就是在那個initialValue裡面)放進去就好了,最後返回這個值。由此可知每個線程都是有用一塊空間來保存這個變量的,所以是線程隔離的,互不干擾的。

至於set()方法其實是類似的,看源碼就能知道了。

remove()方法提供主動刪除不再使用的threadLocal對應的Entry,這樣能避免出現內存洩漏。

在getEntry()中也有調用getEntryAfterMiss()來保障能清理掉stale(陳舊)的Entry,能在很大程度上解決內存洩漏問題。

這裡說只要線程還存活,都有持有一個隱式的引用到threadLocal變量,這個引用鏈為:thread->threadLocalMap->Entry->threadLocal,當線程結束後,其所有的線程局部實例的副本都會被GC回收(除非還存在其他的引用到這些副本)。

  非常感謝你能看到最後,如果有什麼意見或建議,歡迎大家指出,我會積極改進的。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved