Android開發之ThreadLocal原理深入理解


[Android]ThreadLocal的定義和用途

ThreadLocal用於實現在不同的線程中存儲線程私有數據的類。在多線程的環境中,當多個線程需要對某個變量進行頻繁操作,同時各個線程間不需要同步,此時,各個子線程只需要對存儲在當前線程中的變量的拷貝進行操作即可,程序的運行效率會很高,即所謂的空間換時間。

在開源框架EventBus和android系統的Looper類當中有運用到ThreadLocal進行線程局部數據的存儲,android版的ThreadLocal和java原生的ThreadLocal有一定的差別,android版的進行了一些優化設計,通過內部類Values中的Object數組來存儲ThreadLocal的弱引用和線程的局部數據對象;而java版的是以MAP的方式來存儲。

[Android]ThreadLocal的基本使用

ThreadLocal的使用很簡單,定義一個全局的ThreadLocal對象,在線程中通過get()方法得到當前線程的局部數據進行操作。

ThreadLocal<ClassType> storageDataThreadLocal = new ThreadLocal<ClassType>(){
    @Override
    protected ClassType initialValue() {
        return new ClassType();
    }
};

ClassType為自定義的數據類,實例化ThreadLocal對象過程中如果沒有重寫initialValue方法則第一次調用get方法會返回空,這里需要注意。

[Android]ThreadLocal實現線程數據拷貝的原理

我們關注最多的應該就是ThreadLocal如何實現線程的局部數據存儲,通過分析源碼可以得到最終的答案。

我們直接分析get函數:

/**
 * Returns the value of this variable for the current thread. If an entry
 * doesn't yet exist for this variable on this thread, this method will
 * create an entry, populating the value with the result of
 * {@link #initialValue()}.
 *
 * @return the current value of the variable for the calling thread.
 */
@SuppressWarnings("unchecked")
public T get() {
    // Optimized for the fast path.
    //通過native方法得到代碼當前執行的線程對象
    Thread currentThread = Thread.currentThread();
    //得到當前線程的Values類型對象
    Values values = values(currentThread);
    if (values != null) {
    	  //得到對象數組table,用來存儲當前ThreadLocal對象的弱引用
    	  //和當前線程的局部數據對象引用
        Object[] table = values.table;
        //得到用於存儲ThreadLocal弱引用的散列值索引
        int index = hash & values.mask;
        //指向到對象相同,則返回其引用
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
    	  //新建values對象賦給當前線程對象的localValues引用
        values = initializeValues(currentThread);
    }

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

如果當前線程對象內的Values對象中有存儲和當前ThreadLocal對象的reference屬性是同一個對象的引用則返回此線程的本地數據,即變量在線程的本地拷貝。

否則在當前線程內部創建一個Values對象,通過Values.getAfterMiss將ThreadLocal類的initialValue方法得到的本地數據存儲到當前線程對象內部並返回;initialValue是protected方法,如果子類沒有重寫,則默認返回空。

靜態內部類Values的getAfterMiss函數:

/**
* Gets value for given ThreadLocal after not finding it in the first
* slot.
*/
Object getAfterMiss(ThreadLocal<?> key) {
	Object[] table = this.table;
	//通過散列算法得到ThreadLocal的first slot的索引值
   int index = key.hash & mask;

   // If the first slot is empty, the search is over.
   if (table[index] == null) {
   	 //如果first slot上沒有存儲 則將ThreadLocal的弱引用和本地數據
   	 //存儲到table數組的相鄰位置並返回本地數據對象引用
       Object value = key.initialValue();

       // If the table is still the same and the slot is still empty...
       if (this.table == table && table[index] == null) {
             table[index] = key.reference;
             table[index + 1] = value;
             size++;

             cleanUp();
             return value;
         }

         // The table changed during initialValue().
         //遍歷table數組,根據不同判斷將ThreadLocal的
         //弱引用和本地數據對象引用存儲到table數組的相應位置
         put(key, value);
         return value;
     }

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

     // Continue search.
     for (index = next(index);; index = next(index)) {
         Object reference = table[index];
         if (reference == key.reference) {
             return table[index + 1];
         }

         // If no entry was found...
         if (reference == null) {
             Object value = key.initialValue();

             // If the table is still the same...
             if (this.table == table) {
                 // If we passed a tombstone and that slot still
                 // contains a tombstone...
                 if (firstTombstone > -1
                         && table[firstTombstone] == TOMBSTONE) {
                     table[firstTombstone] = key.reference;
                     table[firstTombstone + 1] = value;
                     tombstones--;
                     size++;

                     // No need to clean up here. We aren't filling
                     // in a null slot.
                     return value;
                 }

                 // If this slot is still empty...
                 if (table[index] == null) {
                     table[index] = key.reference;
                     table[index + 1] = value;
                     size++;

                     cleanUp();
                     return value;
                 }
             }

             // The table changed during initialValue().
             put(key, value);
             return value;
         }

         if (firstTombstone == -1 && reference == TOMBSTONE) {
             // Keep track of this tombstone so we can overwrite it.
             firstTombstone = index;
         }
     }
 }

getAfterMiss函數根據不同的判斷將ThreadLocal的弱引用和當前線程的本地對象以類似MAP的方式,存儲在table數組的相鄰位置,其中其散列的索引hash值是通過hashCounter.getAndAdd(0x61c88647 * 2)算法來得到。

set函數:

/**
 * Sets the value of this variable for the current thread. If set to
 * {@code null}, the value will be set to null and the underlying entry will
 * still be present.
 *
 * @param value the new value of the variable for the caller thread.
 */
public void set(T value) {
    Thread currentThread = Thread.currentThread();
    //得到當前線程的本地Values對象
    Values values = values(currentThread);
    if (values == null) {
    	  //為空則新建Values對象
        values = initializeValues(currentThread);
    }
    //將當前ThreadLocal對象引用和本地數據存儲到
    //當前ThreadLocal的內部Values對象的table數組當中
    //當調用get方法時,獲得的即是當前線程的本地數據
    values.put(this, value);
}

[Android]關於ThreadLocal內存泄漏的問題

在其他的博文中有說到,java版的ThreadLocal“value值因為存在一條從current thread連接過來的強引用,只有當前thread結束以后, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.”.

在android版的ThreadLocal中value值不存在強引用,故在table數組中的指定位置置為null,本地數據則沒有存在指向其的引用,當GC回收的時候會清理掉。

[Android]ThreadLocal總結

ThreadLocal實現線程本地存儲的原理是比較清晰的,即在當前線程中調用get方法時,通過ThreadLocal的initialValue方法創建當前線程的一個本地數據拷貝,將此拷貝添加到當前線程本地數據的table數組當中;或者在調用set方法時,將當前線程的本地數據存儲到當前線程的table數組中.當前線程通過調用ThreadLocal對象的get方法即得到當前線程本地數據對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM