亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java中關于ThreadLocal的隱式引用詳解

 更新時間:2024年03月23日 09:32:52   作者:程序猿進階  
這篇文章主要介紹了Java中關于ThreadLocal的隱式引用,從線程的角度看,每個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的,ThreadLocal實例就是可訪問的,下面我們來具體看看

前言

ThreadLocal 并不是一個Thread,而是 ThreadLocalVariable(線程局部變量)。也許把它命名為 ThreadLocalVar更加合適。線程局部變量就是為每一個使用該變量的線程都提供一個變量值的副本,是 Java中一種較為特殊的線程綁定機制,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。ThreadLocal是除了加鎖這種同步方式之外的另一種保證多線程訪問出現線程不安全的方式。

從線程的角度看,每個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的,ThreadLocal實例就是可訪問的;在線程消失之后,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。

通過ThreadLocal存取的數據,總是與當前線程相關,也就是說,JVM 為每個運行的線程,綁定了私有的本地實例存取空間,從而為多線程環(huán)境常出現的并發(fā)訪問問題提供了一種隔離機制。在 ThreadLocal類中有一個 Map,用于存儲每一個線程變量的副本,Map中元素的鍵為線程對象,而值為對應線程的變量副本。ThreadLocal采用了 “以空間換時間” 的方式。為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

一、API說明

  • new ThreadLocal(): 創(chuàng)建一個線程本地變量。
  • T get(): 返回此線程局部變量的當前線程副本中的值,如果這是線程第一次調用該方法,則創(chuàng)建并初始化此副本。
  • void set(T value): 將此線程局部變量的當前線程副本中的值設置為指定值。許多應用程序不需要這項功能,它們只依賴于 initialValue() 方法來設置線程局部變量的值。
  • void remove(): 移除此線程局部變量的值。這可能有助于減少線程局部變量的存儲需求。如果再次訪問此線程局部變量,那么在默認情況下它將擁有其 initialValue。
  • protected T initialValue(): 返回此線程局部變量的當前線程的初始值。最多在每次訪問線程來獲得每個線程局部變量時調用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。如果線程先調用 set(T) 方法再調用 get() 方法,則不會在線程中再調用 initialValue() 方法。若該實現只返回 null;如果程序員希望將線程局部變量初始化為 null 以外的某個值,則必須為 ThreadLocal 創(chuàng)建子類,并重寫此方法。通常,將使用匿名內部類。initialValue 的典型實現將調用一個適當的構造方法,并返回新構造的對象。

在程序中一般都重寫 initialValue方法,以給定一個特定的初始值。

二、ThreadLocal簡單使用

下面的例子中,開啟兩個線程,在每個線程內部設置了本地變量的值,然后調用 print方法打印當前本地變量的值。如果在打印之后調用本地變量的 remove方法會刪除本地內存中的變量,代碼如下所示:

package test;
public class ThreadLocalTest {
    static ThreadLocal<String> localVar = new ThreadLocal<>();
    static void print(String str) {
        //打印當前線程中本地內存中本地變量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地內存中的本地變量
        localVar.remove();
    }
    public static void main(String[] args) {
        Thread t1  = new Thread(new Runnable() {
            @Override
            public void run() {
                //設置線程1中本地變量的值
                localVar.set("localVar1");
                //調用打印方法
                print("thread1");
                //打印本地變量
                System.out.println("after remove : " + localVar.get());
            }
        });
        Thread t2  = new Thread(new Runnable() {
            @Override
            public void run() {
                //設置線程1中本地變量的值
                localVar.set("localVar2");
                //調用打印方法
                print("thread2");
                //打印本地變量
                System.out.println("after remove : " + localVar.get());
            }
        });
        t1.start();
        t2.start();
    }
}

下面是運行后的結果:

thread1 :localVar1
after remove : null
thread2 :localVar2
after remove : null

三、ThreadLocal的實現原理

下面是 ThreadLocal的類圖結構,從圖中可知:Thread類中有兩個變量 threadLocals和 inheritableThreadLocals,二者都是 ThreadLocal內部類 ThreadLocalMap類型的變量,我們通過查看內部內 ThreadLocalMap可以發(fā)現實際上它類似于一個HashMap。在默認情況下,每個線程中的這兩個變量都為null。

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

只有當線程第一次調用 ThreadLocal的 set或者 get方法的時候才會創(chuàng)建他們(后面我們會查看這兩個方法的源碼)。除此之外,每個線程的本地變量不是存放在 ThreadLocal實例中,而是放在調用線程的 ThreadLocals變量里面(前面也說過,該變量是Thread類的變量)。也就是說,ThreadLocal類型的本地變量是存放在具體的線程空間上,其本相當于一個裝載本地變量的工具殼,通過 set方法將 value添加到調用線程的 threadLocals中,當調用線程調用 get方法時候能夠從它的 threadLocals中取出變量。如果調用線程一直不終止,那么這個本地變量將會一直存放在他的 threadLocals中,所以不使用本地變量的時候需要調用 remove方法將 threadLocals中刪除不用的本地變量。下面我們通過查看 ThreadLocal的set、get以及remove方法來查看 ThreadLocal具體實怎樣工作的。

set方法源碼

public void set(T value) {
    //(1)獲取當前線程(調用者線程)
    Thread t = Thread.currentThread();
    //(2)以當前線程作為key值,去查找對應的線程變量,找到對應的map
    ThreadLocalMap map = getMap(t);
    //(3)如果map不為null,就直接添加本地變量,key為當前線程,值為添加的本地變量值
    if (map != null)
        map.set(this, value);
    //(4)如果map為null,說明首次添加,需要首先創(chuàng)建出對應的map
    else
        createMap(t, value);
}

在上面的代碼中,(2)處調用 getMap方法獲得當前線程對應的 threadLocals(參照上面的圖示和文字說明),該方法代碼如下:

ThreadLocalMap getMap(Thread t) {
   return t.threadLocals; //獲取線程自己的變量threadLocals,并綁定到當前調用線程的成員變量threadLocals上
}

如果調用 getMap方法返回值不為 null,就直接將 value值設置到 threadLocals中(key為當前線程引用,值為本地變量);如果getMap方法返回 null說明是第一次調用 set方法(前面說到過,threadLocals默認值為null,只有調用 set方法的時候才會創(chuàng)建map),這個時候就需要調用 createMap方法創(chuàng)建 threadLocals,該方法如下所示:createMap方法不僅創(chuàng)建了threadLocals,同時也將要添加的本地變量值添加到了 threadLocals中。

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

get方法源碼: 在 get方法的實現中,首先獲取當前調用者線程,如果當前線程的 threadLocals不為null,就直接返回當前線程綁定的本地變量值,否則執(zhí)行 setInitialValue方法初始化 threadLocals變量。在 setInitialValue方法中,類似于 set方法的實現,都是判斷當前線程的 threadLocals變量是否為null,是則添加本地變量(這個時候由于是初始化,所以添加的值為null),否則創(chuàng)建 threadLocals變量,同樣添加的值為null。

public T get() {
    //(1)獲取當前線程
    Thread t = Thread.currentThread();
    //(2)獲取當前線程的threadLocals變量
    ThreadLocalMap map = getMap(t);
    //(3)如果threadLocals變量不為null,就可以在map中查找到本地變量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //(4)執(zhí)行到此處,threadLocals為null,調用該更改初始化當前線程的threadLocals變量
    return setInitialValue();
}
private T setInitialValue() {
    //protected T initialValue() {return null;}
    T value = initialValue();
    //獲取當前線程
    Thread t = Thread.currentThread();
    //以當前線程作為key值,去查找對應的線程變量,找到對應的map
    ThreadLocalMap map = getMap(t);
    //如果map不為null,就直接添加本地變量,key為當前線程,值為添加的本地變量值
    if (map != null)
        map.set(this, value);
    //如果map為null,說明首次添加,需要首先創(chuàng)建出對應的map
    else
        createMap(t, value);
    return value;
}

remove方法的實現: remove方法判斷當前線程對應的 threadLocals變量是否為null,不為 null就直接刪除當前線程中指定的 threadLocals變量。

public void remove() {
    //獲取當前線程綁定的threadLocals
     ThreadLocalMap m = getMap(Thread.currentThread());
     //如果map不為null,就移除當前線程中指定ThreadLocal實例的本地變量
     if (m != null)
         m.remove(this);
}

如下圖所示: 每個線程內部有一個名為 threadLocals的成員變量,該變量的類型為 ThreadLocal.ThreadLocalMap類型(類似于一個HashMap),其中的 key為當前定義的 ThreadLocal變量的 this引用,value為我們使用 set方法設置的值。每個線程的本地變量存放在自己的本地內存變量 threadLocals中,如果當前線程一直不消亡,那么這些本地變量就會一直存在(可能會導致內存溢出),因此使用完畢需要將其 remove掉。

四、ThreadLocal不支持繼承性

同一個 ThreadLocal變量在父線程中被設置值后,在子線程中是獲取不到的。(threadLocals 中為當前調用線程對應的本地變量,所以二者自然是不能共享的)

package test;
public class ThreadLocalTest2 {
    //(1)創(chuàng)建ThreadLocal變量
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        //在main線程中添加main線程的本地變量
        threadLocal.set("mainVal");
        //新創(chuàng)建一個子線程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("子線程中的本地變量值:"+threadLocal.get());
            }
        });
        thread.start();
        //輸出main線程中的本地變量值
        System.out.println("mainx線程中的本地變量值:"+threadLocal.get());
    }
}

五、InheritableThreadLocal類

在上面說到的 ThreadLocal類是不能提供子線程訪問父線程的本地變量的,而 InheritableThreadLocal類則可以做到這個功能,下面是該類的源碼

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

從上面代碼可以看出,InheritableThreadLocal類繼承了 ThreadLocal類,并重寫了childValue、getMap、createMap三個方法。其中 createMap方法在被調用(當前線程調用 set方法時得到的 map為 null的時候需要調用該方法)的時候,創(chuàng)建的是inheritableThreadLocal而不是 threadLocals。同理,getMap方法在當前調用者線程調用 get方法的時候返回的也不是threadLocals 而是 inheritableThreadLocal。下面我們看看重寫的 childValue方法在什么時候執(zhí)行,怎樣讓子線程訪問父線程的本地變量值。我們首先從 Thread類開始說起

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //判斷名字的合法性
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    this.name = name;
    //(1)獲取當前線程(父線程)
    Thread parent = currentThread();
    //安全校驗
    SecurityManager security = System.getSecurityManager();
    if (g == null) { //g:當前線程組
        if (security != null) {
            g = security.getThreadGroup();
        }
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }
    g.checkAccess();
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
    g.addUnstarted();
    this.group = g; //設置為當前線程組
    this.daemon = parent.isDaemon();//守護線程與否(同父線程)
    this.priority = parent.getPriority();//優(yōu)先級同父線程
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    //(2)如果父線程的inheritableThreadLocal不為null
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //(3)設置子線程中的inheritableThreadLocals為父線程的inheritableThreadLocals
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    this.stackSize = stackSize;
    tid = nextThreadID();
}

在 init方法中,首先(1)處獲取了當前線程(父線程),然后(2)處判斷當前父線程的 inheritableThreadLocals是否為null,然后調用createInheritedMap將父線程的 inheritableThreadLocals作為構造函數參數創(chuàng)建了一個新的 ThreadLocalMap變量,然后賦值給子線程。下面是 createInheritedMap方法和 ThreadLocalMap的構造方法

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //調用重寫的方法
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

在構造函數中將父線程的 inheritableThreadLocals成員變量的值賦值到新的 ThreadLocalMap對象中。返回之后賦值給子線程的inheritableThreadLocals??傊琁nheritableThreadLocals類通過重寫getMap和createMap兩個方法將本地變量保存到了具體線程的inheritableThreadLocals變量中,當線程通過InheritableThreadLocals實例的set或者get方法設置變量的時候,就會創(chuàng)建當前線程的inheritableThreadLocals變量。而父線程創(chuàng)建子線程的時候,ThreadLocalMap中的構造函數會將父線程的inheritableThreadLocals中的變量復制一份到子線程的inheritableThreadLocals變量中。

六、從ThreadLocalMap看ThreadLocal使用不當的內存泄漏問題

基礎概念 : 首先我們先看看 ThreadLocalMap的類圖,在前面的介紹中,我們知道 ThreadLocal只是一個工具類,他為用戶提供get、set、remove接口操作實際存放本地變量的 threadLocals(調用線程的成員變量),也知道 threadLocals是一個ThreadLocalMap類型的變量,下面我們來看看 ThreadLocalMap這個類。在此之前,我們回憶一下 Java中的四種引用類型鏈接

分析 ThreadLocalMap內部實現: 上面我們知道 ThreadLocalMap內部實際上是一個 Entry數組 private Entry[] table ?,我們先看看 Entry的這個內部類

/**
 * 是繼承自WeakReference的一個類,該類中實際存放的key是指向ThreadLocal的弱引用和與之對應的value值(該value值
 * 就是通過ThreadLocal的set方法傳遞過來的值)由于是弱引用,當get方法返回null的時候意味著回收引用
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** value就是和ThreadLocal綁定的 */
    Object value;
    //k:ThreadLocal的引用,被傳遞給WeakReference的構造方法
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
//WeakReference構造方法(public class WeakReference<T> extends Reference<T> )
public WeakReference(T referent) {
    super(referent); //referent:ThreadLocal的引用
}
//Reference構造方法
Reference(T referent) {
    this(referent, null);//referent:ThreadLocal的引用
}
Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

在上面的代碼中,我們可以看出,當前 ThreadLocal的引用 k被傳遞給 WeakReference的構造函數,所以 ThreadLocalMap中的key為 ThreadLocal的弱引用。當一個線程調用ThreadLocal的 set方法設置變量的時候,當前線程的 ThreadLocalMap就會存放一個記錄,這個記錄的 key值為 ThreadLocal的弱引用,value就是通過 set設置的值。如果當前線程一直存在且沒有調用該ThreadLocal的 remove方法,如果這個時候別的地方還有對 ThreadLocal的引用,那么當前線程中的 ThreadLocalMap中會存在對ThreadLocal變量的引用和 value對象的引用,是不會釋放的,就會造成內存泄漏。

考慮這個 ThreadLocal變量沒有其他強依賴,如果當前線程還存在,由于線程的 ThreadLocalMap里面的 key是弱引用,所以當前線程的 ThreadLocalMap里面的 ThreadLocal變量的弱引用在 gc的時候就被回收,但是對應的 value還是存在的這就可能造成內存泄漏(因為這個時候 ThreadLocalMap會存在key為 null但是 value不為 null的 entry項 )。

ThreadLocalMap 中的 Entry的 key使用的是 ThreadLocal對象的弱引用,在沒有其他地方對 ThreadLoca依賴,ThreadLocalMap中的 ThreadLocal對象就會被回收掉,但是對應的 value不會被回收,這個時候 Map中就可能存在 key為null但是 value不為null的項,這需要實際使用的時候使用完畢及時調用 remove方法避免內存泄漏。

如果使用線程池,由于線程可能并不是真正的關閉(比如newFixedThreadPool會保持線程一只存活)。因此,如果將一些大對象存放到 ThreadLocalMap中,可能會造成內存泄漏。因為線程沒有關閉,無法回收,但是這些對象不會再被使用了。如果希望及時回收對象,則可以使用Thread.remove()方法將變量移除。

以上就是Java中關于ThreadLocal的隱式引用詳解的詳細內容,更多關于Java ThreadLocal的資料請關注腳本之家其它相關文章!

相關文章

  • Java并發(fā)編程之Executor接口的使用

    Java并發(fā)編程之Executor接口的使用

    今天給大家?guī)淼氖顷P于Java并發(fā)編程的相關知識,文章圍繞著Executor接口的使用展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • Java實現拆箱和裝箱的原理解析

    Java實現拆箱和裝箱的原理解析

    Java 是一種強類型語言,然而在 Java中Integer類型和 int類型兩種不同類型的數字卻能正常地進行數學運算,為什么?今天我們就來聊聊其背后的秘密:拆箱和裝箱,感興趣的小伙伴跟著小編一起來看看吧
    2024-05-05
  • Java高效調試排查代碼技巧詳解

    Java高效調試排查代碼技巧詳解

    這篇文章主要介紹了Java高效調試排查代碼技巧,調試是一項不可或缺的技能,無論你是經驗豐富的開發(fā)者,還是初入編程世界的新手,都難免會遇到代碼出錯的情況,有效的調試能幫助我們快速定位并解決問題,提高開發(fā)效率,需要的朋友可以參考下
    2025-04-04
  • JAVA集合框架Map特性及實例解析

    JAVA集合框架Map特性及實例解析

    這篇文章主要介紹了JAVA集合框架Map特性及實例解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • Linux下java環(huán)境配置圖文方法

    Linux下java環(huán)境配置圖文方法

    這篇文章主要介紹了Linux下java環(huán)境配置圖文方法,需要的朋友可以參考下
    2023-06-06
  • Spring Boot修改內置Tomcat默認端口號的示例

    Spring Boot修改內置Tomcat默認端口號的示例

    本篇文章主要介紹了Spring Boot修改內置Tomcat端口號的示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • 淺談IDEA實用的Servlet模板

    淺談IDEA實用的Servlet模板

    今天給大家分享一下IDEA實用的Servlet模板,文中有非常詳細的圖文介紹及代碼示例,對小伙伴們很有幫助哦,需要的朋友可以參考下
    2021-05-05
  • Maven依賴沖突原因以及解決方法

    Maven依賴沖突原因以及解決方法

    依賴沖突是指項目依賴的某一個 jar 包,有多個不同的版本,因而造成類包版本沖突依賴沖突很經常是類包之間的間接依賴引起的,本文將給大家介紹Maven依賴沖突原因以及解決方法,需要的朋友可以參考下
    2023-12-12
  • Java 策略模式與模板方法模式相關總結

    Java 策略模式與模板方法模式相關總結

    這篇文章主要介紹了Java 策略模式與模板方法模式相關總結,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2021-01-01
  • 淺談Spring的屬性編輯器的使用

    淺談Spring的屬性編輯器的使用

    這篇文章主要介紹了淺談Spring的屬性編輯器的使用,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05

最新評論