天天看點

android-----ThreadLocal源碼分析

        今天在複習Handler消息處理機制原理的時候,發現自己對android的ThreadLocal部分了解還不是很到位,在此做個總結,先來說說為什麼會在Handler消息處理機制中出現ThreadLocal這個東西吧,我們都知道Handler發送消息到MessageQueue中,Looper從MessageQueue中取出消息來執行,但是Looper呢隻是一個封裝類而已,雖然它裡面有loop方法,但是真正的運作還是需要線程的,那麼Looper是怎麼和線程聯系到一起的呢?就是采用ThreadLocal了,之前我分析過java中的ThreadLocal源碼,參見:java-----ThreadLocal源碼分析,這篇我們分析的是android中的ThreadLocal源碼,大家都知道解決多線程并發問題我們可以采用synchronized關鍵字來實作,他依托的是JVM的鎖機制來實作臨界區的變量、函數在CPU運作中通路的原子性;而ThreadLocal則是在每個線程中各自都保留一個共享内容的副本,自己修改自己的當然不會出現問題了,也就是說兩者還是有本質差別的,使用synchronized之後所有線程修改的是同一個内容,一個線程對其的修改是會影響到另一個線程的通路該共享内容初始值的,隻不過是按順序修改而已,而采用ThreadLocal的話一個線程修改的内容對另一個線程來說是并不可見的,android中的ThreadLocal在java原生ThreadLocal的基礎上進行了一些 優化操作,個人認為最大的優化地方在于采用數組的方式存放ThreadLocal引用以及值而不是之前的map方式;

        和原生态的ThreadLocal一樣,android中的ThreadLocal源碼最重要的方法也是set和get方法,首先看看set方法:

public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
           

        首先擷取到目前正在執行的線程,并且通過values方法擷取目前線程的Values值,其中Values是定義在ThreadLocal中的靜态類,他的功能就是存儲放進來的資料以及對資料進行處理,比如清理失效資料,擴充table數組等等,他是通過Object數組來進行存放資料的,及時清理無效的資料并且擴充或者縮小table的大小,便于保持目前table的健壯性,檢視values方法:

Values values(Thread current) {
        return current.localValues;
    }
           

        可以發現他僅僅是傳回目前線程的localValues屬性值而已,進入到Thread類中我們看到有如下代碼:

ThreadLocal.Values localValues;
           

        即localValues是Values類型的變量,回到set方法中,在通過values方法獲得Values對象之後,判斷該對象是否為空,如果為空的話,則執行initializeValues方法:

Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }
           

        可以看到該方法僅僅隻是建立一個Values對象出來,并且将其賦給目前線程的localValues屬性;

        回到set方法中,最後我們執行了values的put方法:

void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }
           

        在分析put方法之前我們需要了解一點,在ThreadLocal類中定義了一個Reference變量:

private final Reference<ThreadLocal<T>> reference
            = new WeakReference<ThreadLocal<T>>(this);
           

        他是一個弱引用,引用了ThreadLocal執行個體自己;

        簡單分析下put方法,從這段源碼中我們看到了大部分的操作都是在修改table數組内容,他是一個Object類型的數組,定義在Values方法中,是他的一個屬性,長度總是2乘方倍數,ThreadLocal以及其對應的值都是連續的存儲在這個數組中的,具體來說ThreadLocal的reference值存儲在其對應值的前一個位置處,通過key.hash & mask擷取到目前ThreadLocal在table數組中的index值,關于key.hash & mask的奇妙之處就在于它能夠保證擷取到的index值不會越界同時呢也能保證對于不同的ThreadLocal得到的index值是不同的這一點,具體是通過斐波拉契散列尋址方式實作的,其中mask即計算下标的掩碼,其值為table的長度減1,從上面的put方法中我們最能夠直覺的看出ThreadLocal的值在table數組中的存儲位置為此ThreadLocal的reference字段所辨別對象的下一個位置,比如:

table[index] = key.reference;
 table[index + 1] = value;
           

那麼index存儲的就是目前ThreadLocal的reference弱引用,index+1存儲的就是目前ThreadLocal的值;

        其實這點在Values的add方法中是最能直覺的看出來的:

void add(ThreadLocal<?> key, Object value) {
            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];
                if (k == null) {
                    table[index] = key.reference;
                    table[index + 1] = value;
                    return;
                }
            }
        }
           

        接下來看看他的get方法:

public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }
           

        同樣首先擷取目前正在執行的線程,接着擷取目前線程的localValues屬性值,這個和set方法是一樣的,如果目前線程的localValues屬性值不為空的話,則取出他的table數組,并且找出目前ThreadLocal對象的reference對應的index值,傳回table數組中index+1位置上的值,也就是對應于目前ThreadLocal的值;如果目前線程的localValues屬性值為空的話,則執行initializeValues方法,建立一個Values對象出來,并且通過values的getAfterMiss方法傳回一個初始值對象,簡單檢視getAfterMiss源碼之後發現這個初始值是通過下面代碼擷取的:

Object value = key.initialValue();
           

        也就是調用的ThreadLocal的initialValue方法,這個方法是protected修飾的方法,我們也可以通過繼承ThreadLocal類來重寫這個方法,預設情況下是傳回null的;

protected T initialValue() {
        return null;
    }
           

        至此,android中的ThreadLocal主要源碼分析完畢,下面做個小結:

        (1)android中的ThreadLocal值是存儲在Values靜态類中的table數組中的,這個數組中的0,2,4....2n下标存儲的是ThreadLocal對應的reference值,而1,3,5....(2n+1)下标存儲的是對應于reference的ThreadLocal值,某種意義上這裡的table數組是類似于map方式的;

        (2)尋址的時候是采用斐波那契散列的方式獲得index值的