真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Android進階你必須要了解的知識:ThreadLocal

1、ThreadLocal是什么?

ThreadLocal是一個線程內(nèi)部數(shù)據(jù)存儲類,通過他可以在指定的線程中存儲數(shù)據(jù)。存儲后,只能在指定的線程中獲取到存儲的數(shù)據(jù),對其他線程來說無法獲取到數(shù)據(jù)。

創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價比汝州網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式汝州網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務覆蓋汝州地區(qū)。費用合理售后完善,10余年實體公司更值得信賴。

2、ThreadLocal的使用場景

日常使用場景不多,當某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時候,可以考慮使用ThreadLocal。
Android源碼的Lopper、ActivityThread以及AMS中都用到了ThreadLocal。

3、ThreadLocal的使用示例

public class ThreadLocalActivity extends AppCompatActivity {
private ThreadLocal name = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_thread_local);
    name.set("小明");
    Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
    new Thread("thread1") {
        @Override
        public void run() {
            name.set("小紅");
            Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
        }
    }.start();
    new Thread("thread2") {
        @Override
        public void run() {
            Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
        }
    }.start();
}
}

運行結(jié)果:

D/ThreadLocalActivity: Thread:main name:小明  
D/ThreadLocalActivity: Thread:thread1 name:小紅  
D/ThreadLocalActivity: Thread:thread2 name:null

可以看到雖然訪問的是同一個ThreadLocal對象,但是獲取到的值卻是不一樣的。

4、ThreadLocal的源碼閱讀

那么為什么會造成這樣的結(jié)果呢?這就需要去看看ThreadLocal的源碼實現(xiàn),這里的源碼版本為API 28。主要看它的get和set方法。
set方法:

 public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

set方法中首先獲取了當前線程對象,然后通過getMap方法傳入當前線程t獲取到一個ThreadLocalMap,接下來判斷這個map是否為空,不為空就直接將當前ThreadLocal作為key,set方法中傳入要保存的值最為value,存放到map中;如果map為空就調(diào)用createMap方法創(chuàng)建一個map并同樣將當前ThreadLocal和要保存的值作為key和value加入到map中。
接下先看getMap方法:

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

getMap方法比較簡單,就是返回從傳入的當前線程對象的成員變量threadLocals。
接著是createMap方法:

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

createMap方法也很簡單就是new了一個ThreadLocalMap并賦給當前線程對象t中的threadLocals。
原來這個Map是存放在Thread類中的。于是進入Thread類中查看。
Thread.java第188-190行:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

根據(jù)這里的注釋可以得知,每個線程Thread中都有一個ThreadLocalMap類型的threadLocals成員變量來保存數(shù)據(jù),通過ThreadLocal類來進行維護。這樣看來我們每次在不同線程調(diào)用ThreadLocal的set方法set的數(shù)據(jù)是存在不同線程的ThreadLocalMap中的,就像注釋說的ThreadLocal只是起了個維護ThreadLocalMap的功能。想到是get方法同樣也是到不同線程的ThreadLocalMap去取數(shù)據(jù)。
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();
}

果然,get方法中同樣是先獲取當前線程對象,然后在拿著這個對象t去獲取到t中的ThreadLocalMap,只要map不等于null就調(diào)用map.getEntry(this)方法來獲取數(shù)據(jù),因為ThreadLocalMap里使用一個內(nèi)部類Entry來存儲數(shù)據(jù)的,所以調(diào)用getEntry(this)方法,傳入的key是當前的ThreadLocal。這樣獲取到Entry類型數(shù)據(jù)e,只要e不為null,返回e.value即先前存儲的數(shù)據(jù)。如果獲取到的map為null又或者根據(jù)key獲取Entry為null,就調(diào)用setInitialValue方法初始化一個value返回。
setInitialValue和initialValue方法:

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

 protected T initialValue() {
    return null;
}

setInitialValue方法中首先調(diào)用initialValue方法初始化了一個空value,之后的操作和set方法相同,將這個空的value加入到當前線程的ThreadLocalMap中去,ThreadLocalMap為空就創(chuàng)建個Map,最后返回這個空值。
至此,ThreadLocal的get、set方法就都看過了,也理解了ThreadLocal可以在多個線程中操作而互不干擾的原因。但是ThreadLocal還有一個要注意的地方就是ThreadLocal使用不當會造成內(nèi)存泄漏。

5、ThreadLocal內(nèi)存泄漏的原因

內(nèi)存泄漏的根本原因是當一個對象已經(jīng)不需要再使用本該被回收時,另外一個正在使用的對象持有它的引用從而導致它不能被回收,導致本該被回收的對象不能被回收而停留在堆內(nèi)存中。那么ThreadLocal中是在哪里發(fā)生的呢?這就要看到ThreadLocalMap中存儲數(shù)據(jù)的內(nèi)部類Entry。

   static class Entry extends WeakReference> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }

可以看到這個Entry類,這里的key是使用了個弱引用,所以因為使用弱引用這里的key,ThreadLocal會在JVM下次GC回收時候被回收,而造成了個key為null的情況,而外部ThreadLocalMap是沒辦法通過null key來找到對應value的。如果當前線程一直在運行,那么線程中的ThreadLocalMap也就一直存在,而map中卻存在key已經(jīng)被回收為null對應的Entry和value卻一直存在不會被回收,造成內(nèi)存的泄漏。
不過,這一點設(shè)計者也考慮到了,在get()、set()、remove()方法調(diào)用的時候會清除掉線程ThreadLocalMap中所有Entry中Key為null的Value,并將整個Entry設(shè)置為null,這樣在下次回收時就能將Entry和value回收。
這樣看上去好像是因為key使用了弱引用才導致的內(nèi)存泄漏,為了解決還特意添加了清除null key的功能,那么是不是不用弱引用就可以了呢?

很顯然不是這樣的。設(shè)計者使用弱引用是由原因的。

  • 如果使用強引用,那么如果在運行的線程中ThreadLocal對象已經(jīng)被回收了但是ThreadLocalMap還持有ThreadLocal的強引用,若是沒有手動刪除,ThreadLocal不會被回收,同樣導致內(nèi)存泄漏。
  • 如果使用弱引用ThreadLocal的對象被回收了,因為ThreadLocalMap持有的是ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。nullkey的value在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。

所以,由于ThreadLocalMap和線程Thread的生命周期一樣長,如果沒有手動刪除Map的中的key,無論使用強引用還是弱引用實際上都會出現(xiàn)內(nèi)存泄漏,但是使用弱引用可以多一層保護,null key在下一次ThreadLocalMap調(diào)用set、get、remove的時候就會被清除。
因此,ThreadLocal的內(nèi)存內(nèi)泄漏的真正原因并不能說是因為ThreadLocalMap的key使用了弱引用,而是因為ThreadLocalMap和線程Thread的生命周期一樣長,沒有手動刪除Map的中的key才會導致內(nèi)存泄漏。所以解決ThreadLocal的內(nèi)存泄漏問題就要每次使用完ThreadLocal,都要記得調(diào)用它的remove()方法來清除。


新聞名稱:Android進階你必須要了解的知識:ThreadLocal
URL標題:http://weahome.cn/article/jejhos.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部