国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android 詳解ThreadLocal及InheritableThreadLocal

2019-12-12 03:59:42
字體:
來源:轉載
供稿:網友

 Android  詳解ThreadLocal及InheritableThreadLocal

概要:

因為在android中經常用到handler來處理異步任務,通常用于接收消息,來操作UIThread,其中提到涉及到的looper對象就是保存在Threadlocal中的,因此研究下Threadlocal的源碼。

  分析都是基于android sdk 23 源碼進行的,ThreadLocal在android和jdk中的實現可能并不一致。

  在最初使用Threadlocal的時候,很容易會產生的誤解就是threadlocal就是一個線程。

  首先來看下Threadlocal的簡單例子:

  一個簡單的Person類:

public class Person {  public String name;  public int age;  public Person(String name, int age) {    this.name = name;    this.age = age;  }}

   一個簡單的activity:

 public class MainActivity extends Activity {  //ThreadLocal初始化  private ThreadLocal<Person> mThreadLocal = new ThreadLocal<Person>();  private Person mPerson = new Person("王大俠", 100);  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    //將mPerson對象設置進去    mThreadLocal.set(mPerson);    Log.d("主線程", " 名字:" + mThreadLocal.get().name + " 年齡:" + mThreadLocal.get().age);    }  }

  運行看看是否能得到mperson對象:

04-19 13:14:31.053 2801-2801/com.example.franky.myapplication d/主線程:   名字:王大俠  年齡:100

  果然得到了!說明當前線程確實已經存儲了mPerson對象的引用。

  那么我們開啟個子線程看看適合還能獲取到mPerson對象呢:

public class MainActivity extends Activity {//ThreadLocal初始化private ThreadLocal<Person> mThreadLocal = new ThreadLocal<Person>();private Person mPerson = new Person("王大俠", 100);@Overrideprotected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  //將mPerson對象設置進去  mThreadLocal.set(mPerson);  new Thread(new Runnable() {    @Override    public void run() {      Log.d("子線程", " 名字:" + mThreadLocal.get().name + " 年齡:" + mThreadLocal.get().age);    }  }).start();}}

運行看看結果:

`java.lang.NullPointerException: Attempt to read from field 'java.lang.String com.example.franky.myapplication.Person.name' on a null object reference`

  哈哈,報錯信息很明顯,空指針異常,這清楚的表明子線程是獲取不到mperson對象的,但可能到這里一些朋友可能有些暈了,明明我通過set()方法將mperson設置給threadlocal對象了啊,為啥在這里get()不到呢?

   這里我們開始追蹤threadlocal的源碼看看有沒有線索來解釋這個疑問。

首先我們可以看看threadlocal的構造方法:

/** * Creates a new thread-local variable. */public ThreadLocal() {}

  構造方法平淡無奇,那么我們瞅瞅threadlocal的類說明吧,看看有沒有發現:

implements a thread-local storage, that is, a variable for which each thread * has its own value. all threads share the same {@code threadlocal} object, * but each sees a different value when accessing it, and changes made by one * thread do not affect the other threads. the implementation supports * {@code null} values.

個人英文其實不是很好,大致的意思是每個線程都能在自己的線程保持一個對象,如果在一個線程改變對象的屬性不會影響其他線程。但我們不要誤讀,如果某個對象是共享變量,那么在某個線程中改變它時,其他線程訪問的時候其實該對象也被改變了,所以并不是說ThreadLocal是線程安全的,我們只要理解ThreadLocal是能在當前線程保存一個對象的,這樣我們不用到處傳遞這個對象。

那ThreadLocal是線程嗎?其實看看threadlocal有沒有繼承thread類就知道了:

public class ThreadLocal<T> {}

答案是沒有~~,這說明threadlocal并不是線程。

明白了這點,那我們繼續往下看看ThreadLocal是如何將對象保存起來的,瞅瞅set()方法:

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

首先通過Thread currentthread = thread.currentthread();獲取到當前線程

然后currentthread作為方法參數傳遞給了vlaues方法:

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

這里我們看到return的是thread類的一個成員變量,我們瞅瞅Thread類中的這個變量:

ThreadLocal.Values localValues;

這里我們看到localvalues成員變量的類型就是ThreadLocal.Values

這個類其實是ThreadLocal的內部類。

然后這里判斷得到的values對象是不是null,也就是說Thread類中的成員變量localvalues是不是null,由于我們是初次設置,所以這個對象肯定是null,那繼續走

values initializevalues(thread current) {  return current.localvalues = new values();}

很明顯直接給localvalues變量new了一個value對象。那我們看看values對象里有啥:

首先看看構造:

 Values() {    initializeTable(INITIAL_SIZE);    this.size = 0;    this.tombstones = 0;  }

看起來是初始化了一些成員變量的值,INITIAL_SIZE的值為16,

看看initializeTable(INITIAL_SIZE)這個方法是做啥的:

 private void initializeTable(int capacity) {    this.table = new Object[capacity * 2];    this.mask = table.length - 1;    this.clean = 0;    this.maximumLoad = capacity * 2 / 3; // 2/3  }

初始化了長度為32的table數組,mask為31,clean為0,maximumLoad為10。

又是一堆成員變量,那只好看看變量的說明是做啥的:

這個table很簡單就是個object[]類型,意味著可以存放任何對象,變量說明:

  /**   * Map entries. Contains alternating keys (ThreadLocal) and values.   * The length is always a power of 2.   */  private Object[] table;

??!原來這里就是存放保存的對象的。

其他的變量再看看:

/** Used to turn hashes into indices. */  private int mask; /** Number of live entries. */  private int size; /** Number of tombstones. */  private int tombstones; /** Maximum number of live entries and tombstones. */  private int maximumLoad; /** Points to the next cell to clean up. */  private int clean;

這樣看來mask是用來計算數組下標的,size其實是存活的保存的對象數量,tombstones是過時的對象數量,maximumLoad是最大的保存數量,clean是指向的下一個要清理的位置。大概明白了這些我們再繼續看:

values.put(this, value);

繼續追蹤:

/**   * Sets entry for given ThreadLocal to given value, creating an   * entry if necessary.   */  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;      }    }  }

該方法直接將this對象和要保存的對象傳遞了進來,

第一行的cleanUp()其實是用來對table執行清理的,比如清理一些過時的對象,檢查是否對象的數量是否超過設置值,或者擴容等,這里不再細說,有興趣大家可以研究下。

然后利用key.hash&mask計算下標,這里key.hash的初始化值:

private static AtomicInteger hashCounter = new AtomicInteger(0);private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);

然后注釋說為了確保計算的下標指向的是key值而不是value,當然為啥用上述數值進行計算就能保證獲取的key值,貌似是和這個0x61c88647數值有關,再深入的大家可以留言。

然后最重要的就是

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

這里將自身的引用,而且是弱引用,放在了table[index]上,將value放在它的下一個位置,保證key和value是排列在一起的,這樣其實我們知道了key其實是threadlocal的引用,值是value,它們一同被放置在table數組內。

所以現在的情況是這樣,Thread類中的成員變量localValues是ThreadLocal.Values類型,所以說白了就是當前線程持有了ThreadLocal.Values這樣的數據結構,我們設置的value全部都存儲在里面了,當然如果我們在一個線程中new了很多ThreadLocal對象,其實指向都是Thread類中的成員變量localValues,而且如果new了很多ThreadLocal對象,其實都是放在table中的不同位置的。

那接下來看看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);}

代碼比較簡單了,首先還是獲取當前線程,然后獲取當前線程的Values對象,也就是Thread類中的成員變量localValues,然后拿到Values對象的table數組,計算下標,獲取保存的對象,當然如果沒有獲取到return (T) values.getAfterMiss(this),就是返回null了,其實看方法Object getAfterMiss(ThreadLocal<?> key)中的這個代碼:

Object value = key.initialValue();  protected T initialValue() {  return null;}

就很清楚了,當然我們可以復寫這個方法來實現自定義返回,大家有興趣可以試試。

到此我們再回過頭來看看開始的疑問,為啥mThreadLocal在子線程獲取不到mPerson對象呢?原因就在于子線程獲取自身線程中的localValues變量中并未保存mPerson,真正保存的是主線程,所以我們是獲取不到的。

看完了ThreadLocal我們再看看它的一個子類InheritableThreadLocal,該類和ThreadLocal最大的不同就是它可以在子線程獲取到保存的對象,而ThreadLocal只能在同一個線程,我們看看簡單的例子:

public class MainActivity extends Activity {private InheritableThreadLocal<Person> mInheritableThreadLocal = new InheritableThreadLocal<Person>();private Person mPerson = new Person("王大俠", 100);@Overrideprotected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  //將mPerson設置到當前線程  mInheritableThreadLocal.set(mPerson);  Log.d("主線程"+Thread.currentThread().getName(), " 名字:" + mInheritableThreadLocal.get().name + " 年齡:" + mInheritableThreadLocal.get().age);  new Thread(new Runnable() {    @Override    public void run() {      Log.d("子線程"+Thread.currentThread().getName(), " 名字:" + mInheritableThreadLocal.get().name + " 年齡:" + mInheritableThreadLocal.get().age);    }  }).start();}}

運行看看輸出:

04-21 13:09:11.046 19457-19457/com.example.franky.myapplication D/主線程main:  名字:王大俠 年齡:10004-21 13:09:11.083 19457-21729/com.example.franky.myapplication D/子線程Thread-184:  名字:王大俠 年齡:100

很明顯在子線程也獲取到了mPerson對象,那它是如何實現的呢?
看下源碼:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {/** * Creates a new inheritable thread-local variable. */public InheritableThreadLocal() {}/** * Computes the initial value of this thread-local variable for the child * thread given the parent thread's value. Called from the parent thread when * creating a child thread. The default implementation returns the parent * thread's value. * * @param parentValue the value of the variable in the parent thread. * @return the initial value of the variable for the child thread. */protected T childValue(T parentValue) {  return parentValue;}@OverrideValues values(Thread current) {  return current.inheritableValues;}@OverrideValues initializeValues(Thread current) {  return current.inheritableValues = new Values();}}

很明顯InheritableThreadLocal重寫了兩個方法:

Values values(Thread current)方法返回了Thread類中的成員變量inheritableValues。

Values initializeValues(Thread current)也是new的對象也是指向inheritableValues。

而ThreadLocal中都是指向的localValues這個變量。

也就是說當我們調用set(T value)方法時,根據前面的分析,其實初始化的是這個inheritableValues,那么既然子線程能夠獲取到保存的對象,那我們看看這個變量在Thread類中哪里有調用,搜索下就看到:

private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {  ...  // Transfer over InheritableThreadLocals.  if (currentThread.inheritableValues != null) {    inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);  }  // add ourselves to our ThreadGroup of choice  this.group.addThread(this);}

在Thread類中的create方法中可以看到,該方法在Thread構造方法中被調用,如果currentThread.inheritableValues不為空,就會將它傳遞給Values的有參構造:

 /**   * Used for InheritableThreadLocals.   */  Values(Values fromParent) {    this.table = fromParent.table.clone();    this.mask = fromParent.mask;    this.size = fromParent.size;    this.tombstones = fromParent.tombstones;    this.maximumLoad = fromParent.maximumLoad;    this.clean = fromParent.clean;    inheritValues(fromParent);  }

這里可以看到將inheritableValues的值完全復制過來了,所以我們在子線程一樣可以獲取到保存的變量,我們的分析就到此為止吧。

自己總結的肯定有很多紕漏,還請大家多多指正。

 感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 社旗县| 莲花县| 土默特左旗| 扎囊县| 鲁甸县| 诸暨市| 武强县| 中江县| 正宁县| 贺州市| 新绛县| 汝城县| 鸡泽县| 东方市| 阿克苏市| 龙岩市| 廉江市| 彭泽县| 会理县| 含山县| 报价| 余庆县| 二连浩特市| 炎陵县| 百色市| 通州区| 三河市| 郯城县| 满洲里市| 榕江县| 河西区| 四会市| 德惠市| 易门县| 云和县| 安吉县| 镇原县| 扶风县| 宜城市| 女性| 三台县|