久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

ThreadLocal原理及使用示例

 小豬佩奇爸爸 2020-04-27

簡介:本文以一個(gè)簡要的代碼示例介紹ThreadLocal類的基本使用,,在此基礎(chǔ)上結(jié)合圖片闡述它的內(nèi)部工作原理,,最后分析了ThreadLocal的內(nèi)存泄露問題以及解決方法。

歡迎探討,,如有錯(cuò)誤敬請(qǐng)指正

如需轉(zhuǎn)載,,請(qǐng)注明出處 http://www.cnblogs.com/nullzx/


1. ThreadLocal<T> 簡介和使用示例

ThreadLocal只有一個(gè)無參的構(gòu)造方法

1
public ThreadLocal()

ThreadLocal的相關(guān)方法

1
2
3
4
public T get()
public void set(T value)
public void remove()
protected T initialValue()

initialValue方法的訪問修飾符是protected,該方法為第一次調(diào)用get方法提供一個(gè)初始值,。默認(rèn)情況下,,第一次調(diào)用get方法返回值null。在使用時(shí),,我們一般會(huì)復(fù)寫ThreadLocal的initialValue方法,使第一次調(diào)用get方法時(shí)返回一個(gè)我們?cè)O(shè)定的初始值,。

下面是一個(gè)ThreadLocal的一個(gè)簡單使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package javalearning;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class ThreadLocalDemo {
    /*定義了1個(gè)ThreadLocal<Integer>對(duì)象,,
     *并復(fù)寫它的initialValue方法,初始值是3*/
    private ThreadLocal<Integer> tlA = new ThreadLocal<Integer>(){
        protected Integer initialValue(){
            return 3;
        }
    };
     
    /* 
    private ThreadLocal<Integer> tlB = new ThreadLocal<Integer>(){
        protected Integer initialValue(){
            return 5;
        }
    };
    */
     
    /*設(shè)置一個(gè)信號(hào)量,,許可數(shù)為1,讓三個(gè)線程順序執(zhí)行*/
    Semaphore semaphore = new Semaphore(1);
     
    private Random rnd = new Random();
     
    /*Worker定義為內(nèi)部類實(shí)現(xiàn)了Runnable接口,tlA定義在外部類中,,
每個(gè)線程中調(diào)用這個(gè)對(duì)象的get方法,,再調(diào)用一個(gè)set方法設(shè)置一個(gè)隨機(jī)值*/
    public class Worker implements Runnable{
        @Override
        public void run(){
             
            try {
                Thread.sleep(rnd.nextInt(1000)); /*隨機(jī)延時(shí)1s以內(nèi)的時(shí)間*/
                semaphore.acquire();/*獲取許可*/
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
             
            int valA = tlA.get();
            System.out.println(Thread.currentThread().getName() +' tlA initial val : '+ valA);
            valA = rnd.nextInt();
            tlA.set(valA);
            System.out.println(Thread.currentThread().getName() +' tlA  new     val: '+ valA);
             
            /*
            int valB = tlB.get();
            System.out.println(Thread.currentThread().getName() +' tlB initial val : '+ valB);
            valB = rnd.nextInt();
            tlA.set(valB);
            System.out.println(Thread.currentThread().getName() +' tlB 2    new val: '+ valB);
            */
             
            semaphore.release();
             
            /*在線程池中,當(dāng)線程退出之前一定要記得調(diào)用remove方法,因?yàn)樵诰€程池中的線程對(duì)象是循環(huán)使用的*/
            tlA.remove();
            /*tlB.remove();*/
        }
    }
     
    /*創(chuàng)建三個(gè)線程,,每個(gè)線程都會(huì)對(duì)ThreadLocal對(duì)象tlA進(jìn)行操作*/
    public static void main(String[] args){
        ExecutorService es = Executors.newFixedThreadPool(3);
        ThreadLocalDemo tld = new ThreadLocalDemo();
        es.execute(tld.new Worker());
        es.execute(tld.new Worker());
        es.execute(tld.new Worker());
        es.shutdown();
    }
}

運(yùn)行結(jié)果

1
2
3
4
5
6
pool-1-thread-1 tlA initial val : 3
pool-1-thread-1 tlA  new     val: -1288455998
pool-1-thread-3 tlA initial val : 3
pool-1-thread-3 tlA  new     val: 112537197
pool-1-thread-2 tlA initial val : 3
pool-1-thread-2 tlA  new     val: -12271334

從運(yùn)行結(jié)果可以看出,,每個(gè)線程第一次調(diào)用TheadLocal對(duì)象的get方法時(shí)都得到初始值3,注意我們上面的代碼是讓三個(gè)線程順序執(zhí)行,,顯然從運(yùn)行結(jié)果看,,pool-1-thread-1線程結(jié)束后設(shè)置的新值,對(duì)pool-1-thread-3線程是沒有影響的,,pool-1-thread-3線程完成后設(shè)置的新值對(duì)pool-1-thread-2線程也沒有影響,。這就仿佛把ThreadLocal對(duì)象當(dāng)做每個(gè)線程內(nèi)部的對(duì)象一樣,但實(shí)際上tlA對(duì)象是個(gè)外部類對(duì)象,,內(nèi)部類Worker訪問到的是同一個(gè)tlA對(duì)象,,也就是說是被各個(gè)線程共享的。這是如何做到的呢,?我們現(xiàn)在就來看看ThreadLocal對(duì)象的內(nèi)部原理,。

2. ThreadLocal<T>的原理

首先,在Thread類中定義了一個(gè)threadLocals,,它是ThreadLocal.ThreadLocalMap對(duì)象的引用,默認(rèn)值是null,。ThreadLocal.ThreadLocalMap對(duì)象表示了一個(gè)以開放地址形式的散列表。當(dāng)我們?cè)诰€程的run方法中第一次調(diào)用ThreadLocal對(duì)象的get方法時(shí),,會(huì)為當(dāng)前線程創(chuàng)建一個(gè)ThreadLocalMap對(duì)象,。也就是每個(gè)線程都各自有一張獨(dú)立的散列表,以ThreadLocal對(duì)象作為散列表的key,,set方法中的值作為value(第一次調(diào)用get方法時(shí),,以initialValue方法的返回值作為value)。顯然我們可以定義多個(gè)ThreadLocal對(duì)象,,而我們一般將ThreadLocal對(duì)象定義為static類型或者外部類中,。上面所表達(dá)的意思就是,相同的key在不同的散列表中的值必然是獨(dú)立的,,每個(gè)線程都是在各自的散列表中執(zhí)行操作,。

ThreadLocal_thumb4

TheadLocal中的get源代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//這里的this是指當(dāng)前的ThreadLocal對(duì)象
        if (e != null) {
            @SuppressWarnings('unchecked')
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

 3. 由ThreadLocal造成的內(nèi)存泄露和相應(yīng)解決辦法

ThreadLocalMap 中用內(nèi)部靜態(tài)類Entry表示了散列表中的每一個(gè)條目,下面是它的代碼

1
2
3
4
5
6
7
8
9
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

可以看出Entry類繼承了WeakRefrence類,,所以一個(gè)條目就是一個(gè)弱引用類型的對(duì)象(要搞清楚,,持有weakRefrence對(duì)象的引用是個(gè)強(qiáng)引用),,那么這個(gè)weakRefrence對(duì)象保存了誰的弱引用呢?我們看到構(gòu)造函數(shù)中有個(gè)supe(k),,k是ThreadLocal類型對(duì)象,,super表示是調(diào)用父類的構(gòu)造函數(shù)(父類是誰你要想清楚哦?),,所以說一個(gè)entry對(duì)象中存儲(chǔ)了ThreadLocal對(duì)象的弱引用和這個(gè)ThreadLocal對(duì)應(yīng)的value對(duì)象的強(qiáng)引用,。有關(guān)弱引用的相關(guān)內(nèi)容請(qǐng)參考我的另一篇博客《Java中的四種引用以及ReferenceQueue和WeakHashMap的使用示例》

我們現(xiàn)在假設(shè)一種情況,,假設(shè)我們?cè)诰€程的run方法中調(diào)用了一個(gè)方法,,并在這個(gè)方法中創(chuàng)建了ThreadLocal對(duì)象,并使用了他,,內(nèi)存結(jié)構(gòu)示意圖如下,。

image

當(dāng)這個(gè)方法結(jié)束時(shí),這個(gè)方法中創(chuàng)建的ThreadLocal對(duì)象本身(圖中綠色區(qū)域)就被垃圾回收器回收了,,但是線程還沒有結(jié)束,,所以ThreadLocalMap中還存在這個(gè)entry。由于entry中的key(即ThreadLocal對(duì)象)是弱引用類型,,所以此時(shí)調(diào)用entry.get()方法時(shí)就會(huì)返回null,,內(nèi)部結(jié)構(gòu)如下圖所示。

image

從圖中我們可以看到value對(duì)象(紅色區(qū)域)始終不能被回收,,而我們?cè)僖膊粫?huì)使用它了,,這就造成了內(nèi)存泄露。

那Entry中為什么保存的是key的弱引用呢,?其實(shí)這是為了最大程度上減少內(nèi)存泄露,,副作用是同時(shí)減少哈希表中的沖突。當(dāng)ThreadLocal對(duì)象被回收時(shí),,對(duì)應(yīng)entry中的key就自動(dòng)變成null(entry對(duì)象本身不為null),。若此后我們調(diào)用get,set或remove方法時(shí),就會(huì)嘗試刪除key為null的entry,,以釋放value對(duì)象所占用的內(nèi)存,。

我們現(xiàn)在來看看get方法(上面有g(shù)et方法的源代碼)中調(diào)用的getEntry方法。

1
2
3
4
5
6
7
8
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

從源代碼中我們可以看出,,有可能會(huì)調(diào)用getEntryAfterMiss方法,,而在這個(gè)方法中,刪除key為null的Entry對(duì)象,。同理set方法也有類似的行為,而remove方法不僅僅刪除掉參數(shù)ThreadLocal對(duì)象對(duì)應(yīng)的entry,,而且也會(huì)嘗試刪除其它key為null的entry。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    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;
}

但是上述的方式并不能完全解決內(nèi)存泄露問題,,因?yàn)槲覀冊(cè)谶@個(gè)方法結(jié)束的時(shí)候邏輯上不一定必須調(diào)用get方法,,而get方法也不一定執(zhí)行g(shù)etEntryAfterMiss方法,。所以類本身是沒有這個(gè)能力的,我們只能在不再使用某個(gè)ThreadLocal對(duì)象后,,手動(dòng)調(diào)用remoev方法來刪除它,各自線程中調(diào)用共享的ThreadLocal對(duì)象的remove方法,,這對(duì)其它線程是沒有影響的,這個(gè)應(yīng)該不難理解,。在線程池中這就操作是必須的,,不僅僅是內(nèi)存泄露的問題。因?yàn)榫€程池中的線程是重復(fù)使用的,,意味著這個(gè)線程的ThreadLocalMap對(duì)象也是重復(fù)使用的,如果我們不手動(dòng)調(diào)用remove方法,,那么后面的線程就有可能獲取到上個(gè)線程遺留下來的value值,,造成bug。

4. 參考內(nèi)容

[1]. Java并發(fā)編程:深入剖析ThreadLocal

[2]. [Java并發(fā)包學(xué)習(xí)七]解密ThreadLocal

[3]. 深入分析 ThreadLocal 內(nèi)存泄漏問題

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多