1. 程式人生 > >ThreadLocal 原始碼解析和使用

ThreadLocal 原始碼解析和使用

ThreadLocal定義

ThreadLocal是Java語言提供用於支援執行緒區域性變數的類。
ThreadLocal不是為了解決多執行緒訪問共享變數,而是通過為每個執行緒提供一個獨立的變數副本來解決變數併發訪問的衝突問題。

ThreadLocal常用的4個方法

在JDK5.0中,ThreadLocal已經支援泛型,該類的類名已經變為ThreadLocal

public void set(T value)設定當前執行緒的執行緒區域性變數的值
public T get()返回當前執行緒所對應的執行緒區域性變數
public void remove()將當前執行緒區域性變數的值刪除,目的是為了減少記憶體的佔用,該方法是JDK 5.0新增的方法
protected T initialValue()

返回該執行緒區域性變數的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲呼叫方法,線上程第1次呼叫get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的預設實現直接返回一個null。

ThreadLocal使用

用Andorid中的ThreadLocal來舉例,因為Android中的原始碼比較容易理解
定義一個Demo1類

public class Demo1 {
    ThreadLocal<People> local = new ThreadLocal<People>(){
        @Override
protected People initialValue() {//預設返回一個People物件 return new People(); } }; /** * 設定值 */ public void setName(String name) { People people = local.get(); people.name = name; } /** * 獲取值 */ public String getName() { People people = local.get(); return
people.name; } }

定義一個People類

public class People {
    public String name = "zhongguo";
}

在MainActivity中新增按鈕,新增點選時間,列印log

private void btn1Click() {
        Demo1 demo1 = new Demo1();
        String name = demo1.getName();
        Log.d("MainActivity:", "ThreadName:"+Thread.currentThread()+"name:"+name);
        demo1.setName("xiaoMing");

        new Thread(new Runnable() {
            @Override
            public void run() {
                Demo1 demo1 = new Demo1();
                String name = demo1.getName();
                Log.d("MainActivity:", "ThreadName:"+Thread.currentThread()+"name:"+name);
                demo1.setName("xiaoHong");

                //睡2秒
                SystemClock.sleep(2000);
                name = demo1.getName();
                Log.d("MainActivity:", "ThreadName:"+Thread.currentThread()+"name:"+name);
            }
        }).start();

        //睡2秒
        SystemClock.sleep(2000);
        name = demo1.getName();
        Log.d("MainActivity:", "ThreadName:"+Thread.currentThread()+"name:"+name);
    }

列印log:

10-17 14:15:17.227 9397-9397/com.cn.liuyz.threadlocaldemo D/MainActivity:: ThreadName:Thread[main,5,main]name:zhongguo
10-17 14:15:17.229 9397-9487/com.cn.liuyz.threadlocaldemo D/MainActivity:: ThreadName:Thread[Thread-7976,5,main]name:zhongguo
10-17 14:15:19.229 9397-9397/com.cn.liuyz.threadlocaldemo D/MainActivity:: ThreadName:Thread[main,5,main]name:xiaoMing
10-17 14:15:19.231 9397-9487/com.cn.liuyz.threadlocaldemo D/MainActivity:: ThreadName:Thread[Thread-7976,5,main]name:xiaoHong

以上是ThreadLocal的簡單使用,並不難,下面通過原始碼來進一步瞭解ThreadLocal

ThreadLocal原始碼分析

ThreadLocal的get方法

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();
    }

第2行獲取到當前執行執行緒物件的引用
第3行獲取ThreadLocalMap物件,稍後解析
第4-11行如果map不為空,則再獲取ThreadLocalMap.Entry,如果e不為空則拿到e中的value值並返回,稍後解析
第12行獲取初始化值,當map為null或者內部e為空就會走此方法

getMap(t)方法

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

class Thread implements Runnable {
        ....省略....
        //這個物件的是在ThreadLocal中被例項出來的
        ThreadLocal.ThreadLocalMap threadLocals = null;
        ....省略....
   }

可知threadLocals是在Thread中定義,但例項的是在ThreadLocal初始化的

提前看下ThreadLocal中的createMap方法

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

threadLocals物件就是在這個方法中初始化的,並且每個執行緒只會建立一個ThreadLocalMap物件。稍後再看這個物件都做了什麼?

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;
    }

initialValue方法

protected T initialValue() {
        return null;
    }

這個方法我們可以重寫,預設返回為null,在上邊的例子中我們重寫並返回一個People物件
當map不為空時,就儲存到ThreadLocalMap中,以當前的ThreadLocalMap物件為key
當map為空時,呼叫了crateMap方法,就是建立ThreadLocalMap物件,並把value值儲存到ThreadLocalMap中

createMap方法

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

因createMap方法只會呼叫一次,所以每個執行緒只會建立一個ThreadLocalMap物件

ThreadLocalMap靜態內部類

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    //初始容量,必須是2的冪
    private static final int INITIAL_CAPACITY = 16;

    //entry陣列,用於放entry物件的,陣列長度必須是2的冪
    private Entry[] table;

    //預設值
    private int threshold; // Default to 0

    //設定調整Entry陣列大小閾值  超過2/3時要擴容
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    //建構函式
    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是ThreadLocal類中的靜態內部類,例項卻被Thread類持有,相當於每個執行緒只持有一個map

ThreadLocalMap建構函式

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);
    }

第2行建立一個大小為16的Entry陣列
第3行由當前傳進來的ThreadLocal物件和其他值算出來一個索引值
第4行把創建出來的Entry物件儲存到索引值的位置上
第5行根據size的值判斷Entry陣列實際使用了多少
第6行設定超過容量的2/3時,可擴容

說明:這裡只是初始化了大小為16的Entry陣列,並沒有給每個角標賦Entry物件。而是一個執行緒中有多少個區域性變數需要儲存, 就初始化多少個Entry物件來儲存它們。但每個ThreadLocal物件只能儲存一種型別的變數或者一個物件,那初始化大小為16的陣列有什麼用呢, 這其實就是為了滿足我們儲存多種型別變數或者物件用的,只不過我們需要建立多個ThreadLocal物件。

Entry建構函式

static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

Entry物件是用於儲存ThreadLocal物件和value的實體,模擬了map的儲存方式ThreadLocal<\?>為key,Object為value,其實就是物件中儲存了兩個值, 一個ThreadLocal物件,一個ThreadLocal物件物件的Object。ThreadLocal物件獲取方式是通過Reference物件中的get方法獲取的, 因Entry物件把Object值設定成了成員變數,獲取方式可直接new Entry().value。

因Entry繼承WeakReference,所以在Entry構造中通過super(k)將ThreadLocal物件變成一個弱引用的物件,便於記憶體不足時及時回收。

ThreadLocalMap set方法

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //根據ThreadLocal物件計算出索引值
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 //能進來,說明Entry肯定不為空
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;//直接返回值
                    return;
                }

                if (k == null) {//說明被回收了,
                    //因Entry沒有被回收,所以可複用
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //儲存新的Entry物件到Entry陣列中
            tab[i] = new Entry(key, value);
            //便於判斷Entry陣列是否需要擴容
            int sz = ++size;
            //重新做一遍清理,並判斷是否需要擴容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

set方法總結

  1. 根據ThreadLocal物件計算出索引值
  2. 根據索引值獲取到Entry物件,若不為空,則複用Entry物件,若為空,則建立新Entry物件
  3. 重新清理一遍Entry陣列,若需要擴容則擴容

ThreadLocalMap getEntry方法

private Entry getEntry(ThreadLocal<?> key) {
            //根據ThreadLocal物件計算出索引值
            int i = key.threadLocalHashCode & (table.length - 1);
            //獲取Entry物件
            Entry e = table[i];
            //都為true則返回Entry
            if (e != null && e.get() == key)
                return e;
            else
                //說明Entry物件或Entry物件的key已經被回收
                //需要對Entry物件進行回收,否則該value不清理會記憶體洩露
                return getEntryAfterMiss(key, i, e);
        }

ThreadLocalMap getEntryAfterMiss方法

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
            //如果e==null說明已經被回收,則直接返回null
            //如果e!=null,則先獲取ThreadLocal物件
                //如果ThreadLocal物件和傳進了的key相等則返回Entry物件
                //如果k=null則說明已經被回收,則需要對Entry物件進行回收
                //因expungeStaleEntry對entry進行了回收,下次迴圈時e==null了,退出迴圈,返回null
            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;
        }

ThreadLocalMap expungeStaleEntry方法

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            //Entry物件中的value設null,Entry物件設定null,Entry陣列使用率減1
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
           //清理完了之後再繼續迴圈一遍,並重新將entry hash一遍。
           Entry e;
            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;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

getEntry方法總結

  1. 根據ThreadLocal物件計算出索引值,拿到Entry物件
  2. 如果Entry不為空,Entry.get() == key則直接返回Entry物件
  3. 如果Entry為空,說明Entry物件已經被回收,返回null,如果Entry.get() == null,說明Entry物件中key已經被回收,需要對value進行清理否則會記憶體洩露

ThreadLocal總結

  1. 為每個執行緒建立唯一的ThreadLocalMap物件,在物件中初始化大小為16的Entry陣列,為每個執行緒儲存獨立的變數或物件副本
  2. 設定Entry類繼承WeakReference並把ThreadLocal物件傳遞給WeakReference,使ThreadLocal變成弱引用物件,以便於Entry和ThreadLocal物件都易於回收
  3. 在Entry中儲存的ThreadLocal物件如果被回收了,ThreadLocal會通過expungStaleEntry方法來清理value值和Entry物件以防止出現記憶體洩漏

相關推薦

ThreadLocal 原始碼解析使用

ThreadLocal定義 ThreadLocal是Java語言提供用於支援執行緒區域性變數的類。 ThreadLocal不是為了解決多執行緒訪問共享變數,而是通過為每個執行緒提供一個獨立的變數副本來解決變數併發訪問的衝突問題。 ThreadLocal

Java ThreadLocal原始碼解析: ThreadThreadLocal

之前對TreadLocal有所理解,對原理也有所瞭解,但一直不深入,重新整理,希望藉以加深理解和印象。 在Jdk1.8中,ThreadLocal相關程式碼主要分為三部分: Thread,其中Thread中儲存對ThreadLocal.ThreadLocalMap的引用,作為T

Java ThreadLocal原始碼解析: ThreadLocalMap

ThreadLocalMap在比其中Thread和ThreadLocal部分要複雜很多,是ThreadLocal底層儲存和核心資料結構。從整體上將,ThreadLocalMap底層是Entry陣列,key值為ThreadLocal的hash code, 採用線性探測法解決雜湊衝突。 以下

併發程式設計---ThreadLocal原始碼解析

    在遇到執行緒安全問題的時候,我們一般都是使用同步來解決,比如內建鎖、顯示鎖等等。執行緒安全的主要起因是因為多個執行緒同時操作一個共享變數,如果我們換種思路,在某些場景下,我們為這些執行緒提供共享變數的副本,讓他們在自己的私有域中去操作這些變數,執行緒之間互不影響,那是不是

Gin原始碼解析例子——路由

        Gin是一個基於golang的net包實現的網路框架。從github上,我們可以看到它相對於其他框架而言,具有優越的效能。本系列將從應用的角度來解析其原始碼。(轉載請指明出於breaksoftware的csdn部落格)   &

JAVA集合-03ArrayList原始碼解析使用例項

上一章講解了Collection介面下得抽象類和繼承介面,後續深入到具體的實現類,部落格及對應得程式碼可在github上檢視 ArrayList簡介 ArrayList底層實現是陣列,相較於陣列固定大小,ArrayList可以動態的增加;ArrayList繼承AbstractCollect

Gin原始碼解析例子——中介軟體(middleware)

        在《Gin原始碼解析和例子——路由》一文中,我們已經初識中介軟體。本文將繼續探討這個技術。(轉載請指明出於breaksoftware的csdn部落格)         Gin的中介軟體,本質是一個

Java 集合系列03之 ArrayList詳細介紹(原始碼解析)使用示例

概要 上一章,我們學習了Collection的架構。這一章開始,我們對Collection的具體實現類進行講解;首先,講解List,而List中ArrayList又最為常用。因此,本章我們講解ArrayList。先對ArrayList有個整體認識,再學習它的原始

Java 集合系列10之 HashMap詳細介紹(原始碼解析)使用示例

概要 這一章,我們對HashMap進行學習。 我們先對HashMap有個整體認識,然後再學習它的原始碼,最後再通過例項來學會使用HashMap。內容包括: 第1部分 HashMap介紹 第2部分 HashMap資料結構 第3部分 HashMap原始碼解析(基於J

Java 8 ThreadLocal 原始碼解析

Java 中的 ThreadLocal是執行緒內的區域性變數, 它為每個執行緒儲存變數的一個副本。ThreadLocal 物件可以在多個執行緒中共享, 但每個執行緒只能讀寫其中自己的副本。 目錄: 程式碼示例 原始碼解析 InheritableThreadLocal ThreadLoca

Java 集合系列13之 WeakHashMap詳細介紹(原始碼解析)使用示例

1 package java.util; 2 import java.lang.ref.WeakReference; 3 import java.lang.ref.ReferenceQueue; 4 5 public class WeakHashMap<K,V>

Java 集合系列17之 TreeSet詳細介紹(原始碼解析)使用示例

1 package java.util; 2 3 public class TreeSet<E> extends AbstractSet<E> 4 implements NavigableSet<E>, Cloneable, java.i

Java 集合系列11之 Hashtable詳細介紹(原始碼解析)使用示例

1 package java.util; 2 import java.io.*; 3 4 public class Hashtable<K,V> 5 extends Dictionary<K,V> 6 implements Map

Java 集合系列16之 HashSet詳細介紹(原始碼解析)使用示例

1 package java.util; 2 3 public class HashSet<E> 4 extends AbstractSet<E> 5 implements Set<E>, Cloneable, java.i

Java 集合系列07之 Stack詳細介紹(原始碼解析)使用示例

1 import java.util.Stack; 2 import java.util.Iterator; 3 import java.util.List; 4 5 /** 6 * @desc Stack的測試程式。測試常用API的用法 7 * 8 * @autho

Java 集合系列12之 TreeMap詳細介紹(原始碼解析)使用示例

1 package java.util; 2 3 public class TreeMap<K,V> 4 extends AbstractMap<K,V> 5 implements NavigableMap<K,V>, Clone

Java 集合系列06之 Vector詳細介紹(原始碼解析)使用示例

1 package java.util; 2 3 public class Vector<E> 4 extends AbstractList<E> 5 implements List<E>, RandomAccess, Cl

Android Recovery 原始碼解析介面定製

Android Recovery 原始碼解析和介面定製 Recovery主要功能 原始碼路徑和主要原檔案 recoverycpp 命令列引數 main

Spring原始碼解析配置檔案載入

Spring類的繼承結構圖: Spring運用了大量的模板方法模式和策略模式,所以各位看原始碼的時候,務必留意,每一個繼承的層次都有不同的作用,然後將相同的地方抽取出來,依賴抽象將不同的處理按照不同的策略去處理。 步驟A. 讀取 Resource 檔案形成 Documen

Runtime原始碼解析實戰使用

文章目錄 Runtime-原始碼分析 類的結構體 物件的初始化 訊息的傳送機制 objc_msgSend 工作原理 訊息轉發機制